/* utilities.js */

/*
 * Copyright (C) 2007 Apple Inc.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer. 
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution. 
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission. 
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

Object.proxyType = function(objectProxy)
{
    if (objectProxy === null)
        return "null";

    var type = typeof objectProxy;
    if (type !== "object" && type !== "function")
        return type;

    return objectProxy.type;
}

Object.properties = function(obj)
{
    var properties = [];
    for (var prop in obj)
        properties.push(prop);
    return properties;
}

Object.sortedProperties = function(obj, sortFunc)
{
    return Object.properties(obj).sort(sortFunc);
}

Function.prototype.bind = function(thisObject)
{
    var func = this;
    var args = Array.prototype.slice.call(arguments, 1);
    return function() { return func.apply(thisObject, args.concat(Array.prototype.slice.call(arguments, 0))) };
}

Node.prototype.rangeOfWord = function(offset, stopCharacters, stayWithinNode, direction)
{
    var startNode;
    var startOffset = 0;
    var endNode;
    var endOffset = 0;

    if (!stayWithinNode)
        stayWithinNode = this;

    if (!direction || direction === "backward" || direction === "both") {
        var node = this;
        while (node) {
            if (node === stayWithinNode) {
                if (!startNode)
                    startNode = stayWithinNode;
                break;
            }

            if (node.nodeType === Node.TEXT_NODE) {
                var start = (node === this ? (offset - 1) : (node.nodeValue.length - 1));
                for (var i = start; i >= 0; --i) {
                    if (stopCharacters.indexOf(node.nodeValue[i]) !== -1) {
                        startNode = node;
                        startOffset = i + 1;
                        break;
                    }
                }
            }

            if (startNode)
                break;

            node = node.traversePreviousNode(stayWithinNode);
        }

        if (!startNode) {
            startNode = stayWithinNode;
            startOffset = 0;
        }
    } else {
        startNode = this;
        startOffset = offset;
    }

    if (!direction || direction === "forward" || direction === "both") {
        node = this;
        while (node) {
            if (node === stayWithinNode) {
                if (!endNode)
                    endNode = stayWithinNode;
                break;
            }

            if (node.nodeType === Node.TEXT_NODE) {
                var start = (node === this ? offset : 0);
                for (var i = start; i < node.nodeValue.length; ++i) {
                    if (stopCharacters.indexOf(node.nodeValue[i]) !== -1) {
                        endNode = node;
                        endOffset = i;
                        break;
                    }
                }
            }

            if (endNode)
                break;

            node = node.traverseNextNode(stayWithinNode);
        }

        if (!endNode) {
            endNode = stayWithinNode;
            endOffset = stayWithinNode.nodeType === Node.TEXT_NODE ? stayWithinNode.nodeValue.length : stayWithinNode.childNodes.length;
        }
    } else {
        endNode = this;
        endOffset = offset;
    }

    var result = this.ownerDocument.createRange();
    result.setStart(startNode, startOffset);
    result.setEnd(endNode, endOffset);

    return result;
}

Element.prototype.removeStyleClass = function(className) 
{
    // Test for the simple case first.
    if (this.className === className) {
        this.className = "";
        return;
    }

    var index = this.className.indexOf(className);
    if (index === -1)
        return;

    var newClassName = " " + this.className + " ";
    this.className = newClassName.replace(" " + className + " ", " ");
}

Element.prototype.removeMatchingStyleClasses = function(classNameRegex)
{
    var regex = new RegExp("(^|\\s+)" + classNameRegex + "($|\\s+)");
    if (regex.test(this.className))
        this.className = this.className.replace(regex, " ");
}

Element.prototype.addStyleClass = function(className) 
{
    if (className && !this.hasStyleClass(className))
        this.className += (this.className.length ? " " + className : className);
}

Element.prototype.hasStyleClass = function(className) 
{
    if (!className)
        return false;
    // Test for the simple case before using a RegExp.
    if (this.className === className)
        return true;

    var index = this.className.indexOf(className);
    if (index === -1)
        return false;
    var toTest = " " + this.className + " ";
    return toTest.indexOf(" " + className + " ", index) !== -1;
}

Element.prototype.positionAt = function(x, y)
{
    this.style.left = x + "px";
    this.style.top = y + "px";
}

Node.prototype.enclosingNodeOrSelfWithNodeNameInArray = function(nameArray)
{
    for (var node = this; node && node !== this.ownerDocument; node = node.parentNode)
        for (var i = 0; i < nameArray.length; ++i)
            if (node.nodeName.toLowerCase() === nameArray[i].toLowerCase())
                return node;
    return null;
}

Node.prototype.enclosingNodeOrSelfWithNodeName = function(nodeName)
{
    return this.enclosingNodeOrSelfWithNodeNameInArray([nodeName]);
}

Node.prototype.enclosingNodeOrSelfWithClass = function(className)
{
    for (var node = this; node && node !== this.ownerDocument; node = node.parentNode)
        if (node.nodeType === Node.ELEMENT_NODE && node.hasStyleClass(className))
            return node;
    return null;
}

Node.prototype.enclosingNodeWithClass = function(className)
{
    if (!this.parentNode)
        return null;
    return this.parentNode.enclosingNodeOrSelfWithClass(className);
}

Element.prototype.query = function(query) 
{
    return this.ownerDocument.evaluate(query, this, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
}

Element.prototype.removeChildren = function()
{
    this.innerHTML = "";
}

Element.prototype.isInsertionCaretInside = function()
{
    var selection = window.getSelection();
    if (!selection.rangeCount || !selection.isCollapsed)
        return false;
    var selectionRange = selection.getRangeAt(0);
    return selectionRange.startContainer === this || selectionRange.startContainer.isDescendant(this);
}

Element.prototype.__defineGetter__("totalOffsetLeft", function()
{
    var total = 0;
    for (var element = this; element; element = element.offsetParent)
        total += element.offsetLeft;
    return total;
});

Element.prototype.__defineGetter__("totalOffsetTop", function()
{
    var total = 0;
    for (var element = this; element; element = element.offsetParent)
        total += element.offsetTop;
    return total;
});

Element.prototype.offsetRelativeToWindow = function(targetWindow)
{
    var elementOffset = {x: 0, y: 0};
    var curElement = this;
    var curWindow = this.ownerDocument.defaultView;
    while (curWindow && curElement) {
        elementOffset.x += curElement.totalOffsetLeft;
        elementOffset.y += curElement.totalOffsetTop;
        if (curWindow === targetWindow)
            break;

        curElement = curWindow.frameElement;
        curWindow = curWindow.parent;
    }

    return elementOffset;
}

Node.prototype.isWhitespace = isNodeWhitespace;
Node.prototype.displayName = nodeDisplayName;
Node.prototype.isAncestor = function(node)
{
    return isAncestorNode(this, node);
};
Node.prototype.isDescendant = isDescendantNode;
Node.prototype.traverseNextNode = traverseNextNode;
Node.prototype.traversePreviousNode = traversePreviousNode;
Node.prototype.onlyTextChild = onlyTextChild;

String.prototype.hasSubstring = function(string, caseInsensitive)
{
    if (!caseInsensitive)
        return this.indexOf(string) !== -1;
    return this.match(new RegExp(string.escapeForRegExp(), "i"));
}

String.prototype.escapeCharacters = function(chars)
{
    var foundChar = false;
    for (var i = 0; i < chars.length; ++i) {
        if (this.indexOf(chars.charAt(i)) !== -1) {
            foundChar = true;
            break;
        }
    }

    if (!foundChar)
        return this;

    var result = "";
    for (var i = 0; i < this.length; ++i) {
        if (chars.indexOf(this.charAt(i)) !== -1)
            result += "\\";
        result += this.charAt(i);
    }

    return result;
}

String.prototype.escapeForRegExp = function()
{
    return this.escapeCharacters("^[]{}()\\.$*+?|");
}

String.prototype.escapeHTML = function()
{
    return this.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
}

String.prototype.collapseWhitespace = function()
{
    return this.replace(/[\s\xA0]+/g, " ");
}

String.prototype.trimLeadingWhitespace = function()
{
    return this.replace(/^[\s\xA0]+/g, "");
}

String.prototype.trimTrailingWhitespace = function()
{
    return this.replace(/[\s\xA0]+$/g, "");
}

String.prototype.trimWhitespace = function()
{
    return this.replace(/^[\s\xA0]+|[\s\xA0]+$/g, "");
}

String.prototype.trimURL = function(baseURLDomain)
{
    var result = this.replace(new RegExp("^http[s]?:\/\/", "i"), "");
    if (baseURLDomain)
        result = result.replace(new RegExp("^" + baseURLDomain.escapeForRegExp(), "i"), "");
    return result;
}

function isNodeWhitespace()
{
    if (!this || this.nodeType !== Node.TEXT_NODE)
        return false;
    if (!this.nodeValue.length)
        return true;
    return this.nodeValue.match(/^[\s\xA0]+$/);
}

function nodeDisplayName()
{
    if (!this)
        return "";

    switch (this.nodeType) {
        case Node.DOCUMENT_NODE:
            return "Document";

        case Node.ELEMENT_NODE:
            var name = "<" + this.nodeName.toLowerCase();

            if (this.hasAttributes()) {
                var value = this.getAttribute("id");
                if (value)
                    name += " id=\"" + value + "\"";
                value = this.getAttribute("class");
                if (value)
                    name += " class=\"" + value + "\"";
                if (this.nodeName.toLowerCase() === "a") {
                    value = this.getAttribute("name");
                    if (value)
                        name += " name=\"" + value + "\"";
                    value = this.getAttribute("href");
                    if (value)
                        name += " href=\"" + value + "\"";
                } else if (this.nodeName.toLowerCase() === "img") {
                    value = this.getAttribute("src");
                    if (value)
                        name += " src=\"" + value + "\"";
                } else if (this.nodeName.toLowerCase() === "iframe") {
                    value = this.getAttribute("src");
                    if (value)
                        name += " src=\"" + value + "\"";
                } else if (this.nodeName.toLowerCase() === "input") {
                    value = this.getAttribute("name");
                    if (value)
                        name += " name=\"" + value + "\"";
                    value = this.getAttribute("type");
                    if (value)
                        name += " type=\"" + value + "\"";
                } else if (this.nodeName.toLowerCase() === "form") {
                    value = this.getAttribute("action");
                    if (value)
                        name += " action=\"" + value + "\"";
                }
            }

            return name + ">";

        case Node.TEXT_NODE:
            if (isNodeWhitespace.call(this))
                return "(whitespace)";
            return "\"" + this.nodeValue + "\"";

        case Node.COMMENT_NODE:
            return "<!--" + this.nodeValue + "-->";
            
        case Node.DOCUMENT_TYPE_NODE:
            var docType = "<!DOCTYPE " + this.nodeName;
            if (this.publicId) {
                docType += " PUBLIC \"" + this.publicId + "\"";
                if (this.systemId)
                    docType += " \"" + this.systemId + "\"";
            } else if (this.systemId)
                docType += " SYSTEM \"" + this.systemId + "\"";
            if (this.internalSubset)
                docType += " [" + this.internalSubset + "]";
            return docType + ">";
    }

    return this.nodeName.toLowerCase().collapseWhitespace();
}

function isAncestorNode(ancestor, node)
{
    if (!node || !ancestor)
        return false;

    var currentNode = node.parentNode;
    while (currentNode) {
        if (ancestor === currentNode)
            return true;
        currentNode = currentNode.parentNode;
    }
    return false;
}

function isDescendantNode(descendant)
{
    return isAncestorNode(descendant, this);
}

function traverseNextNode(stayWithin)
{
    if (!this)
        return;

    var node = this.firstChild;
    if (node)
        return node;

    if (stayWithin && this === stayWithin)
        return null;

    node = this.nextSibling;
    if (node)
        return node;

    node = this;
    while (node && !node.nextSibling && (!stayWithin || !node.parentNode || node.parentNode !== stayWithin))
        node = node.parentNode;
    if (!node)
        return null;

    return node.nextSibling;
}

function traversePreviousNode(stayWithin)
{
    if (!this)
        return;
    if (stayWithin && this === stayWithin)
        return null;
    var node = this.previousSibling;
    while (node && node.lastChild)
        node = node.lastChild;
    if (node)
        return node;
    return this.parentNode;
}

function onlyTextChild()
{
    if (!this)
        return null;

    var firstChild = this.firstChild;
    if (!firstChild || firstChild.nodeType !== Node.TEXT_NODE)
        return null;

    var sibling = firstChild.nextSibling;
    return sibling ? null : firstChild;
}

function appropriateSelectorForNode(node, justSelector)
{
    if (!node)
        return "";

    var lowerCaseName = node.localName || node.nodeName.toLowerCase();

    var id = node.getAttribute("id");
    if (id) {
        var selector = "#" + id;
        return (justSelector ? selector : lowerCaseName + selector);
    }

    var className = node.getAttribute("class");
    if (className) {
        var selector = "." + className.replace(/\s+/, ".");
        return (justSelector ? selector : lowerCaseName + selector);
    }

    if (lowerCaseName === "input" && node.getAttribute("type"))
        return lowerCaseName + "[type=\"" + node.getAttribute("type") + "\"]";

    return lowerCaseName;
}

function getDocumentForNode(node)
{
    return node.nodeType == Node.DOCUMENT_NODE ? node : node.ownerDocument;
}

function parentNode(node)
{
    return node.parentNode;
}

Number.secondsToString = function(seconds, formatterFunction, higherResolution)
{
    if (!formatterFunction)
        formatterFunction = String.sprintf;

    var ms = seconds * 1000;
    if (higherResolution && ms < 1000)
        return formatterFunction("%.3fms", ms);
    else if (ms < 1000)
        return formatterFunction("%.0fms", ms);

    if (seconds < 60)
        return formatterFunction("%.2fs", seconds);

    var minutes = seconds / 60;
    if (minutes < 60)
        return formatterFunction("%.1fmin", minutes);

    var hours = minutes / 60;
    if (hours < 24)
        return formatterFunction("%.1fhrs", hours);

    var days = hours / 24;
    return formatterFunction("%.1f days", days);
}

Number.bytesToString = function(bytes, formatterFunction, higherResolution)
{
    if (!formatterFunction)
        formatterFunction = String.sprintf;
    if (typeof higherResolution === "undefined")
        higherResolution = true;

    if (bytes < 1024)
        return formatterFunction("%.0fB", bytes);

    var kilobytes = bytes / 1024;
    if (higherResolution && kilobytes < 1024)
        return formatterFunction("%.2fKB", kilobytes);
    else if (kilobytes < 1024)
        return formatterFunction("%.0fKB", kilobytes);

    var megabytes = kilobytes / 1024;
    if (higherResolution)
        return formatterFunction("%.3fMB", megabytes);
    else
        return formatterFunction("%.0fMB", megabytes);
}

Number.constrain = function(num, min, max)
{
    if (num < min)
        num = min;
    else if (num > max)
        num = max;
    return num;
}

HTMLTextAreaElement.prototype.moveCursorToEnd = function()
{
    var length = this.value.length;
    this.setSelectionRange(length, length);
}

Array.prototype.remove = function(value, onlyFirst)
{
    if (onlyFirst) {
        var index = this.indexOf(value);
        if (index !== -1)
            this.splice(index, 1);
        return;
    }

    var length = this.length;
    for (var i = 0; i < length; ++i) {
        if (this[i] === value)
            this.splice(i, 1);
    }
}

function insertionIndexForObjectInListSortedByFunction(anObject, aList, aFunction)
{
    // indexOf returns (-lowerBound - 1). Taking (-result - 1) works out to lowerBound.
    return (-indexOfObjectInListSortedByFunction(anObject, aList, aFunction) - 1);
}

function indexOfObjectInListSortedByFunction(anObject, aList, aFunction)
{
    var first = 0;
    var last = aList.length - 1;
    var floor = Math.floor;
    var mid, c;

    while (first <= last) {
        mid = floor((first + last) / 2);
        c = aFunction(anObject, aList[mid]);

        if (c > 0)
            first = mid + 1;
        else if (c < 0)
            last = mid - 1;
        else {
            // Return the first occurance of an item in the list.
            while (mid > 0 && aFunction(anObject, aList[mid - 1]) === 0)
                mid--;
            first = mid;
            break;
        }
    }

    // By returning 1 less than the negative lower search bound, we can reuse this function
    // for both indexOf and insertionIndexFor, with some simple arithmetic.
    return (-first - 1);
}

String.sprintf = function(format)
{
    return String.vsprintf(format, Array.prototype.slice.call(arguments, 1));
}

String.tokenizeFormatString = function(format)
{
    var tokens = [];
    var substitutionIndex = 0;

    function addStringToken(str)
    {
        tokens.push({ type: "string", value: str });
    }

    function addSpecifierToken(specifier, precision, substitutionIndex)
    {
        tokens.push({ type: "specifier", specifier: specifier, precision: precision, substitutionIndex: substitutionIndex });
    }

    var index = 0;
    for (var precentIndex = format.indexOf("%", index); precentIndex !== -1; precentIndex = format.indexOf("%", index)) {
        addStringToken(format.substring(index, precentIndex));
        index = precentIndex + 1;

        if (format[index] === "%") {
            addStringToken("%");
            ++index;
            continue;
        }

        if (!isNaN(format[index])) {
            // The first character is a number, it might be a substitution index.
            var number = parseInt(format.substring(index));
            while (!isNaN(format[index]))
                ++index;
            // If the number is greater than zero and ends with a "$",
            // then this is a substitution index.
            if (number > 0 && format[index] === "$") {
                substitutionIndex = (number - 1);
                ++index;
            }
        }

        var precision = -1;
        if (format[index] === ".") {
            // This is a precision specifier. If no digit follows the ".",
            // then the precision should be zero.
            ++index;
            precision = parseInt(format.substring(index));
            if (isNaN(precision))
                precision = 0;
            while (!isNaN(format[index]))
                ++index;
        }

        addSpecifierToken(format[index], precision, substitutionIndex);

        ++substitutionIndex;
        ++index;
    }

    addStringToken(format.substring(index));

    return tokens;
}

String.standardFormatters = {
    d: function(substitution)
    {
        if (typeof substitution == "object" && Object.proxyType(substitution) === "number")
            substitution = substitution.description;
        substitution = parseInt(substitution);
        return !isNaN(substitution) ? substitution : 0;
    },

    f: function(substitution, token)
    {
        if (typeof substitution == "object" && Object.proxyType(substitution) === "number")
            substitution = substitution.description;
        substitution = parseFloat(substitution);
        if (substitution && token.precision > -1)
            substitution = substitution.toFixed(token.precision);
        return !isNaN(substitution) ? substitution : (token.precision > -1 ? Number(0).toFixed(token.precision) : 0);
    },

    s: function(substitution)
    {
        if (typeof substitution == "object" && Object.proxyType(substitution) !== "null")
            substitution = substitution.description;
        return substitution;
    },
};

String.vsprintf = function(format, substitutions)
{
    return String.format(format, substitutions, String.standardFormatters, "", function(a, b) { return a + b; }).formattedResult;
}

String.format = function(format, substitutions, formatters, initialValue, append)
{
    if (!format || !substitutions || !substitutions.length)
        return { formattedResult: append(initialValue, format), unusedSubstitutions: substitutions };

    function prettyFunctionName()
    {
        return "String.format(\"" + format + "\", \"" + substitutions.join("\", \"") + "\")";
    }

    function warn(msg)
    {
        console.warn(prettyFunctionName() + ": " + msg);
    }

    function error(msg)
    {
        console.error(prettyFunctionName() + ": " + msg);
    }

    var result = initialValue;
    var tokens = String.tokenizeFormatString(format);
    var usedSubstitutionIndexes = {};

    for (var i = 0; i < tokens.length; ++i) {
        var token = tokens[i];

        if (token.type === "string") {
            result = append(result, token.value);
            continue;
        }

        if (token.type !== "specifier") {
            error("Unknown token type \"" + token.type + "\" found.");
            continue;
        }

        if (token.substitutionIndex >= substitutions.length) {
            // If there are not enough substitutions for the current substitutionIndex
            // just output the format specifier literally and move on.
            error("not enough substitution arguments. Had " + substitutions.length + " but needed " + (token.substitutionIndex + 1) + ", so substitution was skipped.");
            result = append(result, "%" + (token.precision > -1 ? token.precision : "") + token.specifier);
            continue;
        }

        usedSubstitutionIndexes[token.substitutionIndex] = true;

        if (!(token.specifier in formatters)) {
            // Encountered an unsupported format character, treat as a string.
            warn("unsupported format character \u201C" + token.specifier + "\u201D. Treating as a string.");
            result = append(result, substitutions[token.substitutionIndex]);
            continue;
        }

        result = append(result, formatters[token.specifier](substitutions[token.substitutionIndex], token));
    }

    var unusedSubstitutions = [];
    for (var i = 0; i < substitutions.length; ++i) {
        if (i in usedSubstitutionIndexes)
            continue;
        unusedSubstitutions.push(substitutions[i]);
    }

    return { formattedResult: result, unusedSubstitutions: unusedSubstitutions };
}

function isEnterKey(event) {
    // Check if in IME.
    return event.keyCode !== 229 && event.keyIdentifier === "Enter";
}

function isFnKey(event) {
    return event.keyCode >= 112 && event.keyCode <= 123;
}

/* treeoutline.js */

/*
 * Copyright (C) 2007 Apple Inc.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer. 
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution. 
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission. 
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

function TreeOutline(listNode)
{
    this.children = [];
    this.selectedTreeElement = null;
    this._childrenListNode = listNode;
    this._childrenListNode.removeChildren();
    this._knownTreeElements = [];
    this._treeElementsExpandedState = [];
    this.expandTreeElementsWhenArrowing = false;
    this.root = true;
    this.hasChildren = false;
    this.expanded = true;
    this.selected = false;
    this.treeOutline = this;
}

TreeOutline._knownTreeElementNextIdentifier = 1;

TreeOutline._appendChild = function(child)
{
    if (!child)
        throw("child can't be undefined or null");

    var lastChild = this.children[this.children.length - 1];
    if (lastChild) {
        lastChild.nextSibling = child;
        child.previousSibling = lastChild;
    } else {
        child.previousSibling = null;
        child.nextSibling = null;
    }

    this.children.push(child);
    this.hasChildren = true;
    child.parent = this;
    child.treeOutline = this.treeOutline;
    child.treeOutline._rememberTreeElement(child);

    var current = child.children[0];
    while (current) {
        current.treeOutline = this.treeOutline;
        current.treeOutline._rememberTreeElement(current);
        current = current.traverseNextTreeElement(false, child, true);
    }

    if (child.hasChildren && child.treeOutline._treeElementsExpandedState[child.identifier] !== undefined)
        child.expanded = child.treeOutline._treeElementsExpandedState[child.identifier];

    if (!this._childrenListNode) {
        this._childrenListNode = this.treeOutline._childrenListNode.ownerDocument.createElement("ol");
        this._childrenListNode.parentTreeElement = this;
        this._childrenListNode.addStyleClass("children");
        if (this.hidden)
            this._childrenListNode.addStyleClass("hidden");
    }

    child._attach();
}

TreeOutline._insertChild = function(child, index)
{
    if (!child)
        throw("child can't be undefined or null");

    var previousChild = (index > 0 ? this.children[index - 1] : null);
    if (previousChild) {
        previousChild.nextSibling = child;
        child.previousSibling = previousChild;
    } else {
        child.previousSibling = null;
    }

    var nextChild = this.children[index];
    if (nextChild) {
        nextChild.previousSibling = child;
        child.nextSibling = nextChild;
    } else {
        child.nextSibling = null;
    }

    this.children.splice(index, 0, child);
    this.hasChildren = true;
    child.parent = this;
    child.treeOutline = this.treeOutline;
    child.treeOutline._rememberTreeElement(child);

    var current = child.children[0];
    while (current) {
        current.treeOutline = this.treeOutline;
        current.treeOutline._rememberTreeElement(current);
        current = current.traverseNextTreeElement(false, child, true);
    }

    if (child.hasChildren && child.treeOutline._treeElementsExpandedState[child.identifier] !== undefined)
        child.expanded = child.treeOutline._treeElementsExpandedState[child.identifier];

    if (!this._childrenListNode) {
        this._childrenListNode = this.treeOutline._childrenListNode.ownerDocument.createElement("ol");
        this._childrenListNode.parentTreeElement = this;
        this._childrenListNode.addStyleClass("children");
        if (this.hidden)
            this._childrenListNode.addStyleClass("hidden");
    }

    child._attach();
}

TreeOutline._removeChildAtIndex = function(childIndex)
{
    if (childIndex < 0 || childIndex >= this.children.length)
        throw("childIndex out of range");

    var child = this.children[childIndex];
    this.children.splice(childIndex, 1);

    child.deselect();

    if (child.previousSibling)
        child.previousSibling.nextSibling = child.nextSibling;
    if (child.nextSibling)
        child.nextSibling.previousSibling = child.previousSibling;

    if (child.treeOutline) {
        child.treeOutline._forgetTreeElement(child);
        child.treeOutline._forgetChildrenRecursive(child);
    }

    child._detach();
    child.treeOutline = null;
    child.parent = null;
    child.nextSibling = null;
    child.previousSibling = null;
}

TreeOutline._removeChild = function(child)
{
    if (!child)
        throw("child can't be undefined or null");

    var childIndex = this.children.indexOf(child);
    if (childIndex === -1)
        throw("child not found in this node's children");

    TreeOutline._removeChildAtIndex.call(this, childIndex);
}

TreeOutline._removeChildren = function()
{
    for (var i = 0; i < this.children.length; ++i) {
        var child = this.children[i];
        child.deselect();

        if (child.treeOutline) {
            child.treeOutline._forgetTreeElement(child);
            child.treeOutline._forgetChildrenRecursive(child);
        }

        child._detach();
        child.treeOutline = null;
        child.parent = null;
        child.nextSibling = null;
        child.previousSibling = null;
    }

    this.children = [];
}

TreeOutline._removeChildrenRecursive = function()
{
    var childrenToRemove = this.children;

    var child = this.children[0];
    while (child) {
        if (child.children.length)
            childrenToRemove = childrenToRemove.concat(child.children);
        child = child.traverseNextTreeElement(false, this, true);
    }

    for (var i = 0; i < childrenToRemove.length; ++i) {
        var child = childrenToRemove[i];
        child.deselect();
        if (child.treeOutline)
            child.treeOutline._forgetTreeElement(child);
        child._detach();
        child.children = [];
        child.treeOutline = null;
        child.parent = null;
        child.nextSibling = null;
        child.previousSibling = null;
    }

    this.children = [];
}

TreeOutline.prototype._rememberTreeElement = function(element)
{
    if (!this._knownTreeElements[element.identifier])
        this._knownTreeElements[element.identifier] = [];

    // check if the element is already known
    var elements = this._knownTreeElements[element.identifier];
    if (elements.indexOf(element) !== -1)
        return;

    // add the element
    elements.push(element);
}

TreeOutline.prototype._forgetTreeElement = function(element)
{
    if (this._knownTreeElements[element.identifier])
        this._knownTreeElements[element.identifier].remove(element, true);
}

TreeOutline.prototype._forgetChildrenRecursive = function(parentElement)
{
    var child = parentElement.children[0];
    while (child) {
        this._forgetTreeElement(child);
        child = child.traverseNextTreeElement(false, this, true);
    }
}

TreeOutline.prototype.getCachedTreeElement = function(representedObject)
{
    if (!representedObject)
        return null;

    if ("__treeElementIdentifier" in representedObject) {
        // If this representedObject has a tree element identifier, and it is a known TreeElement
        // in our tree we can just return that tree element.
        var elements = this._knownTreeElements[representedObject.__treeElementIdentifier];
        if (elements) {
            for (var i = 0; i < elements.length; ++i)
                if (elements[i].representedObject === representedObject)
                    return elements[i];
        }
    }
    return null;
}

TreeOutline.prototype.findTreeElement = function(representedObject, isAncestor, getParent)
{
    if (!representedObject)
        return null;

    var cachedElement = this.getCachedTreeElement(representedObject);
    if (cachedElement)
        return cachedElement;

    // The representedObject isn't know, so we start at the top of the tree and work down to find the first
    // tree element that represents representedObject or one of its ancestors.
    var item;
    var found = false;
    for (var i = 0; i < this.children.length; ++i) {
        item = this.children[i];
        if (item.representedObject === representedObject || isAncestor(item.representedObject, representedObject)) {
            found = true;
            break;
        }
    }

    if (!found)
        return null;

    // Make sure the item that we found is connected to the root of the tree.
    // Build up a list of representedObject's ancestors that aren't already in our tree.
    var ancestors = [];
    var currentObject = representedObject;
    while (currentObject) {
        ancestors.unshift(currentObject);
        if (currentObject === item.representedObject)
            break;
        currentObject = getParent(currentObject);
    }

    // For each of those ancestors we populate them to fill in the tree.
    for (var i = 0; i < ancestors.length; ++i) {
        // Make sure we don't call findTreeElement with the same representedObject
        // again, to prevent infinite recursion.
        if (ancestors[i] === representedObject)
            continue;
        // FIXME: we could do something faster than findTreeElement since we will know the next
        // ancestor exists in the tree.
        item = this.findTreeElement(ancestors[i], isAncestor, getParent);
        if (item && item.onpopulate)
            item.onpopulate(item);
    }

    return this.getCachedTreeElement(representedObject);
}

TreeOutline.prototype.treeElementFromPoint = function(x, y)
{
    var node = this._childrenListNode.ownerDocument.elementFromPoint(x, y);
    var listNode = node.enclosingNodeOrSelfWithNodeNameInArray(["ol", "li"]);
    if (listNode)
        return listNode.parentTreeElement || listNode.treeElement;
    return null;
}

TreeOutline.prototype.handleKeyEvent = function(event)
{
    if (!this.selectedTreeElement || event.shiftKey || event.metaKey || event.ctrlKey)
        return false;

    var handled = false;
    var nextSelectedElement;
    if (event.keyIdentifier === "Up" && !event.altKey) {
        nextSelectedElement = this.selectedTreeElement.traversePreviousTreeElement(true);
        while (nextSelectedElement && !nextSelectedElement.selectable)
            nextSelectedElement = nextSelectedElement.traversePreviousTreeElement(!this.expandTreeElementsWhenArrowing);
        handled = nextSelectedElement ? true : false;
    } else if (event.keyIdentifier === "Down" && !event.altKey) {
        nextSelectedElement = this.selectedTreeElement.traverseNextTreeElement(true);
        while (nextSelectedElement && !nextSelectedElement.selectable)
            nextSelectedElement = nextSelectedElement.traverseNextTreeElement(!this.expandTreeElementsWhenArrowing);
        handled = nextSelectedElement ? true : false;
    } else if (event.keyIdentifier === "Left") {
        if (this.selectedTreeElement.expanded) {
            if (event.altKey)
                this.selectedTreeElement.collapseRecursively();
            else
                this.selectedTreeElement.collapse();
            handled = true;
        } else if (this.selectedTreeElement.parent && !this.selectedTreeElement.parent.root) {
            handled = true;
            if (this.selectedTreeElement.parent.selectable) {
                nextSelectedElement = this.selectedTreeElement.parent;
                handled = nextSelectedElement ? true : false;
            } else if (this.selectedTreeElement.parent)
                this.selectedTreeElement.parent.collapse();
        }
    } else if (event.keyIdentifier === "Right") {
        if (!this.selectedTreeElement.revealed()) {
            this.selectedTreeElement.reveal();
            handled = true;
        } else if (this.selectedTreeElement.hasChildren) {
            handled = true;
            if (this.selectedTreeElement.expanded) {
                nextSelectedElement = this.selectedTreeElement.children[0];
                handled = nextSelectedElement ? true : false;
            } else {
                if (event.altKey)
                    this.selectedTreeElement.expandRecursively();
                else
                    this.selectedTreeElement.expand();
            }
        }
    }

    if (nextSelectedElement) {
        nextSelectedElement.reveal();
        nextSelectedElement.select();
    }

    if (handled) {
        event.preventDefault();
        event.stopPropagation();
    }

    return handled;
}

TreeOutline.prototype.expand = function()
{
    // this is the root, do nothing
}

TreeOutline.prototype.collapse = function()
{
    // this is the root, do nothing
}

TreeOutline.prototype.revealed = function()
{
    return true;
}

TreeOutline.prototype.reveal = function()
{
    // this is the root, do nothing
}

TreeOutline.prototype.appendChild = TreeOutline._appendChild;
TreeOutline.prototype.insertChild = TreeOutline._insertChild;
TreeOutline.prototype.removeChild = TreeOutline._removeChild;
TreeOutline.prototype.removeChildAtIndex = TreeOutline._removeChildAtIndex;
TreeOutline.prototype.removeChildren = TreeOutline._removeChildren;
TreeOutline.prototype.removeChildrenRecursive = TreeOutline._removeChildrenRecursive;

function TreeElement(title, representedObject, hasChildren)
{
    this._title = title;
    this.representedObject = (representedObject || {});

    if (this.representedObject.__treeElementIdentifier)
        this.identifier = this.representedObject.__treeElementIdentifier;
    else {
        this.identifier = TreeOutline._knownTreeElementNextIdentifier++;
        this.representedObject.__treeElementIdentifier = this.identifier;
    }

    this._hidden = false;
    this.expanded = false;
    this.selected = false;
    this.hasChildren = hasChildren;
    this.children = [];
    this.treeOutline = null;
    this.parent = null;
    this.previousSibling = null;
    this.nextSibling = null;
    this._listItemNode = null;
}

TreeElement.prototype = {
    selectable: true,
    arrowToggleWidth: 10,

    get listItemElement() {
        return this._listItemNode;
    },

    get childrenListElement() {
        return this._childrenListNode;
    },

    get title() {
        return this._title;
    },

    set title(x) {
        this._title = x;
        if (this._listItemNode)
            this._listItemNode.innerHTML = x;
    },

    get tooltip() {
        return this._tooltip;
    },

    set tooltip(x) {
        this._tooltip = x;
        if (this._listItemNode)
            this._listItemNode.title = x ? x : "";
    },

    get hasChildren() {
        return this._hasChildren;
    },

    set hasChildren(x) {
        if (this._hasChildren === x)
            return;

        this._hasChildren = x;

        if (!this._listItemNode)
            return;

        if (x)
            this._listItemNode.addStyleClass("parent");
        else {
            this._listItemNode.removeStyleClass("parent");
            this.collapse();
        }
    },

    get hidden() {
        return this._hidden;
    },

    set hidden(x) {
        if (this._hidden === x)
            return;

        this._hidden = x;

        if (x) {
            if (this._listItemNode)
                this._listItemNode.addStyleClass("hidden");
            if (this._childrenListNode)
                this._childrenListNode.addStyleClass("hidden");
        } else {
            if (this._listItemNode)
                this._listItemNode.removeStyleClass("hidden");
            if (this._childrenListNode)
                this._childrenListNode.removeStyleClass("hidden");
        }
    },

    get shouldRefreshChildren() {
        return this._shouldRefreshChildren;
    },

    set shouldRefreshChildren(x) {
        this._shouldRefreshChildren = x;
        if (x && this.expanded)
            this.expand();
    }
}

TreeElement.prototype.appendChild = TreeOutline._appendChild;
TreeElement.prototype.insertChild = TreeOutline._insertChild;
TreeElement.prototype.removeChild = TreeOutline._removeChild;
TreeElement.prototype.removeChildAtIndex = TreeOutline._removeChildAtIndex;
TreeElement.prototype.removeChildren = TreeOutline._removeChildren;
TreeElement.prototype.removeChildrenRecursive = TreeOutline._removeChildrenRecursive;

TreeElement.prototype._attach = function()
{
    if (!this._listItemNode || this.parent._shouldRefreshChildren) {
        if (this._listItemNode && this._listItemNode.parentNode)
            this._listItemNode.parentNode.removeChild(this._listItemNode);

        this._listItemNode = this.treeOutline._childrenListNode.ownerDocument.createElement("li");
        this._listItemNode.treeElement = this;
        this._listItemNode.innerHTML = this._title;
        this._listItemNode.title = this._tooltip ? this._tooltip : "";

        if (this.hidden)
            this._listItemNode.addStyleClass("hidden");
        if (this.hasChildren)
            this._listItemNode.addStyleClass("parent");
        if (this.expanded)
            this._listItemNode.addStyleClass("expanded");
        if (this.selected)
            this._listItemNode.addStyleClass("selected");

        this._listItemNode.addEventListener("mousedown", TreeElement.treeElementSelected, false);
        this._listItemNode.addEventListener("click", TreeElement.treeElementToggled, false);
        this._listItemNode.addEventListener("dblclick", TreeElement.treeElementDoubleClicked, false);

        if (this.onattach)
            this.onattach(this);
    }

    var nextSibling = null;
    if (this.nextSibling && this.nextSibling._listItemNode && this.nextSibling._listItemNode.parentNode === this.parent._childrenListNode)
        nextSibling = this.nextSibling._listItemNode;
    this.parent._childrenListNode.insertBefore(this._listItemNode, nextSibling);
    if (this._childrenListNode)
        this.parent._childrenListNode.insertBefore(this._childrenListNode, this._listItemNode.nextSibling);
    if (this.selected)
        this.select();
    if (this.expanded)
        this.expand();
}

TreeElement.prototype._detach = function()
{
    if (this._listItemNode && this._listItemNode.parentNode)
        this._listItemNode.parentNode.removeChild(this._listItemNode);
    if (this._childrenListNode && this._childrenListNode.parentNode)
        this._childrenListNode.parentNode.removeChild(this._childrenListNode);
}

TreeElement.treeElementSelected = function(event)
{
    var element = event.currentTarget;
    if (!element || !element.treeElement || !element.treeElement.selectable)
        return;

    if (element.treeElement.isEventWithinDisclosureTriangle(event))
        return;

    element.treeElement.select();
}

TreeElement.treeElementToggled = function(event)
{
    var element = event.currentTarget;
    if (!element || !element.treeElement)
        return;

    if (!element.treeElement.isEventWithinDisclosureTriangle(event))
        return;

    if (element.treeElement.expanded) {
        if (event.altKey)
            element.treeElement.collapseRecursively();
        else
            element.treeElement.collapse();
    } else {
        if (event.altKey)
            element.treeElement.expandRecursively();
        else
            element.treeElement.expand();
    }
}

TreeElement.treeElementDoubleClicked = function(event)
{
    var element = event.currentTarget;
    if (!element || !element.treeElement)
        return;

    if (element.treeElement.ondblclick)
        element.treeElement.ondblclick(element.treeElement, event);
    else if (element.treeElement.hasChildren && !element.treeElement.expanded)
        element.treeElement.expand();
}

TreeElement.prototype.collapse = function()
{
    if (this._listItemNode)
        this._listItemNode.removeStyleClass("expanded");
    if (this._childrenListNode)
        this._childrenListNode.removeStyleClass("expanded");

    this.expanded = false;
    if (this.treeOutline)
        this.treeOutline._treeElementsExpandedState[this.identifier] = true;

    if (this.oncollapse)
        this.oncollapse(this);
}

TreeElement.prototype.collapseRecursively = function()
{
    var item = this;
    while (item) {
        if (item.expanded)
            item.collapse();
        item = item.traverseNextTreeElement(false, this, true);
    }
}

TreeElement.prototype.expand = function()
{
    if (!this.hasChildren || (this.expanded && !this._shouldRefreshChildren && this._childrenListNode))
        return;

    if (this.treeOutline && (!this._childrenListNode || this._shouldRefreshChildren)) {
        if (this._childrenListNode && this._childrenListNode.parentNode)
            this._childrenListNode.parentNode.removeChild(this._childrenListNode);

        this._childrenListNode = this.treeOutline._childrenListNode.ownerDocument.createElement("ol");
        this._childrenListNode.parentTreeElement = this;
        this._childrenListNode.addStyleClass("children");

        if (this.hidden)
            this._childrenListNode.addStyleClass("hidden");

        if (this.onpopulate)
            this.onpopulate(this);

        for (var i = 0; i < this.children.length; ++i)
            this.children[i]._attach();

        delete this._shouldRefreshChildren;
    }

    if (this._listItemNode) {
        this._listItemNode.addStyleClass("expanded");
        if (this._childrenListNode && this._childrenListNode.parentNode != this._listItemNode.parentNode)
            this.parent._childrenListNode.insertBefore(this._childrenListNode, this._listItemNode.nextSibling);
    }

    if (this._childrenListNode)
        this._childrenListNode.addStyleClass("expanded");

    this.expanded = true;
    if (this.treeOutline)
        this.treeOutline._treeElementsExpandedState[this.identifier] = true;

    if (this.onexpand)
        this.onexpand(this);
}

TreeElement.prototype.expandRecursively = function(maxDepth)
{
    var item = this;
    var info = {};
    var depth = 0;

    // The Inspector uses TreeOutlines to represents object properties, so recursive expansion
    // in some case can be infinite, since JavaScript objects can hold circular references.
    // So default to a recursion cap of 3 levels, since that gives fairly good results.
    if (typeof maxDepth === "undefined" || typeof maxDepth === "null")
        maxDepth = 3;

    while (item) {
        if (depth < maxDepth)
            item.expand();
        item = item.traverseNextTreeElement(false, this, (depth >= maxDepth), info);
        depth += info.depthChange;
    }
}

TreeElement.prototype.hasAncestor = function(ancestor) {
    if (!ancestor)
        return false;

    var currentNode = this.parent;
    while (currentNode) {
        if (ancestor === currentNode)
            return true;
        currentNode = currentNode.parent;
    }

    return false;
}

TreeElement.prototype.reveal = function()
{
    var currentAncestor = this.parent;
    while (currentAncestor && !currentAncestor.root) {
        if (!currentAncestor.expanded)
            currentAncestor.expand();
        currentAncestor = currentAncestor.parent;
    }

    if (this.onreveal)
        this.onreveal(this);
}

TreeElement.prototype.revealed = function()
{
    var currentAncestor = this.parent;
    while (currentAncestor && !currentAncestor.root) {
        if (!currentAncestor.expanded)
            return false;
        currentAncestor = currentAncestor.parent;
    }

    return true;
}

TreeElement.prototype.select = function(supressOnSelect)
{
    if (!this.treeOutline || !this.selectable || this.selected)
        return;

    if (this.treeOutline.selectedTreeElement)
        this.treeOutline.selectedTreeElement.deselect();

    this.selected = true;
    this.treeOutline.selectedTreeElement = this;
    if (this._listItemNode)
        this._listItemNode.addStyleClass("selected");

    if (this.onselect && !supressOnSelect)
        this.onselect(this);
}

TreeElement.prototype.deselect = function(supressOnDeselect)
{
    if (!this.treeOutline || this.treeOutline.selectedTreeElement !== this || !this.selected)
        return;

    this.selected = false;
    this.treeOutline.selectedTreeElement = null;
    if (this._listItemNode)
        this._listItemNode.removeStyleClass("selected");

    if (this.ondeselect && !supressOnDeselect)
        this.ondeselect(this);
}

TreeElement.prototype.traverseNextTreeElement = function(skipHidden, stayWithin, dontPopulate, info)
{
    if (!dontPopulate && this.hasChildren && this.onpopulate)
        this.onpopulate(this);

    if (info)
        info.depthChange = 0;

    var element = skipHidden ? (this.revealed() ? this.children[0] : null) : this.children[0];
    if (element && (!skipHidden || (skipHidden && this.expanded))) {
        if (info)
            info.depthChange = 1;
        return element;
    }

    if (this === stayWithin)
        return null;

    element = skipHidden ? (this.revealed() ? this.nextSibling : null) : this.nextSibling;
    if (element)
        return element;

    element = this;
    while (element && !element.root && !(skipHidden ? (element.revealed() ? element.nextSibling : null) : element.nextSibling) && element.parent !== stayWithin) {
        if (info)
            info.depthChange -= 1;
        element = element.parent;
    }

    if (!element)
        return null;

    return (skipHidden ? (element.revealed() ? element.nextSibling : null) : element.nextSibling);
}

TreeElement.prototype.traversePreviousTreeElement = function(skipHidden, dontPopulate)
{
    var element = skipHidden ? (this.revealed() ? this.previousSibling : null) : this.previousSibling;
    if (!dontPopulate && element && element.hasChildren && element.onpopulate)
        element.onpopulate(element);

    while (element && (skipHidden ? (element.revealed() && element.expanded ? element.children[element.children.length - 1] : null) : element.children[element.children.length - 1])) {
        if (!dontPopulate && element.hasChildren && element.onpopulate)
            element.onpopulate(element);
        element = (skipHidden ? (element.revealed() && element.expanded ? element.children[element.children.length - 1] : null) : element.children[element.children.length - 1]);
    }

    if (element)
        return element;

    if (!this.parent || this.parent.root)
        return null;

    return this.parent;
}

TreeElement.prototype.isEventWithinDisclosureTriangle = function(event)
{
    var left = this._listItemNode.totalOffsetLeft;
    return event.pageX >= left && event.pageX <= left + this.arrowToggleWidth && this.hasChildren;
}

/* inspector.js */

/*
 * Copyright (C) 2006, 2007, 2008 Apple Inc.  All rights reserved.
 * Copyright (C) 2007 Matt Lilek (pewtermoose@gmail.com).
 * Copyright (C) 2009 Joseph Pecoraro
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

var Preferences = {
    showUserAgentStyles: true,
    maxInlineTextChildLength: 80,
    minConsoleHeight: 75,
    minSidebarWidth: 100,
    minElementsSidebarWidth: 200,
    minScriptsSidebarWidth: 200,
    showInheritedComputedStyleProperties: false,
    styleRulesExpandedState: {},
    showMissingLocalizedStrings: false,
    heapProfilerPresent: false,
    samplingCPUProfiler: false,
    showColorNicknames: true,
    colorFormat: "hex",
    eventListenersFilter: "all",
    resourcesLargeRows: true
}

function preloadImages()
{
    (new Image()).src = "Images/clearConsoleButtonGlyph.png";
    (new Image()).src = "Images/consoleButtonGlyph.png";
    (new Image()).src = "Images/dockButtonGlyph.png";
    (new Image()).src = "Images/enableOutlineButtonGlyph.png";
    (new Image()).src = "Images/enableSolidButtonGlyph.png";
    (new Image()).src = "Images/excludeButtonGlyph.png";
    (new Image()).src = "Images/focusButtonGlyph.png";
    (new Image()).src = "Images/largerResourcesButtonGlyph.png";
    (new Image()).src = "Images/nodeSearchButtonGlyph.png";
    (new Image()).src = "Images/pauseOnExceptionButtonGlyph.png";
    (new Image()).src = "Images/percentButtonGlyph.png";
    (new Image()).src = "Images/recordButtonGlyph.png";
    (new Image()).src = "Images/recordToggledButtonGlyph.png";
    (new Image()).src = "Images/reloadButtonGlyph.png";
    (new Image()).src = "Images/undockButtonGlyph.png";
}

preloadImages();

var WebInspector = {
    resources: {},
    resourceURLMap: {},
    missingLocalizedStrings: {},

    get previousFocusElement()
    {
        return this._previousFocusElement;
    },

    get currentFocusElement()
    {
        return this._currentFocusElement;
    },

    set currentFocusElement(x)
    {
        if (this._currentFocusElement !== x)
            this._previousFocusElement = this._currentFocusElement;
        this._currentFocusElement = x;

        if (this._currentFocusElement) {
            this._currentFocusElement.focus();

            // Make a caret selection inside the new element if there isn't a range selection and
            // there isn't already a caret selection inside.
            var selection = window.getSelection();
            if (selection.isCollapsed && !this._currentFocusElement.isInsertionCaretInside()) {
                var selectionRange = this._currentFocusElement.ownerDocument.createRange();
                selectionRange.setStart(this._currentFocusElement, 0);
                selectionRange.setEnd(this._currentFocusElement, 0);

                selection.removeAllRanges();
                selection.addRange(selectionRange);
            }
        } else if (this._previousFocusElement)
            this._previousFocusElement.blur();
    },

    get currentPanel()
    {
        return this._currentPanel;
    },

    set currentPanel(x)
    {
        if (this._currentPanel === x)
            return;

        if (this._currentPanel)
            this._currentPanel.hide();

        this._currentPanel = x;

        this.updateSearchLabel();

        if (x) {
            x.show();

            if (this.currentQuery) {
                if (x.performSearch) {
                    function performPanelSearch()
                    {
                        this.updateSearchMatchesCount();

                        x.currentQuery = this.currentQuery;
                        x.performSearch(this.currentQuery);
                    }

                    // Perform the search on a timeout so the panel switches fast.
                    setTimeout(performPanelSearch.bind(this), 0);
                } else {
                    // Update to show Not found for panels that can't be searched.
                    this.updateSearchMatchesCount();
                }
            }
        }

        for (var panelName in WebInspector.panels) {
            if (WebInspector.panels[panelName] == x)
                InspectorController.storeLastActivePanel(panelName);
        }
    },

    _createPanels: function()
    {
        var hiddenPanels = (InspectorController.hiddenPanels() || "").split(',');
        if (hiddenPanels.indexOf("elements") === -1)
            this.panels.elements = new WebInspector.ElementsPanel();
        if (hiddenPanels.indexOf("resources") === -1)
            this.panels.resources = new WebInspector.ResourcesPanel();
        if (hiddenPanels.indexOf("scripts") === -1)
            this.panels.scripts = new WebInspector.ScriptsPanel();
        if (hiddenPanels.indexOf("timeline") === -1)
            this.panels.timeline = new WebInspector.TimelinePanel();
        if (hiddenPanels.indexOf("profiles") === -1) {
            this.panels.profiles = new WebInspector.ProfilesPanel();
            this.panels.profiles.registerProfileType(new WebInspector.CPUProfileType());
        }

        if (hiddenPanels.indexOf("storage") === -1 && hiddenPanels.indexOf("databases") === -1)
            this.panels.storage = new WebInspector.StoragePanel();
    },

    _loadPreferences: function()
    {
        var colorFormat = InspectorController.setting("color-format");
        if (colorFormat)
            Preferences.colorFormat = colorFormat;

        var eventListenersFilter = InspectorController.setting("event-listeners-filter");
        if (eventListenersFilter)
            Preferences.eventListenersFilter = eventListenersFilter;

        var resourcesLargeRows = InspectorController.setting("resources-large-rows");
        if (typeof resourcesLargeRows !== "undefined")
            Preferences.resourcesLargeRows = resourcesLargeRows;
    },

    get attached()
    {
        return this._attached;
    },

    set attached(x)
    {
        if (this._attached === x)
            return;

        this._attached = x;

        this.updateSearchLabel();

        var dockToggleButton = document.getElementById("dock-status-bar-item");
        var body = document.body;

        if (x) {
            InspectorController.attach();
            body.removeStyleClass("detached");
            body.addStyleClass("attached");
            dockToggleButton.title = WebInspector.UIString("Undock into separate window.");
        } else {
            InspectorController.detach();
            body.removeStyleClass("attached");
            body.addStyleClass("detached");
            dockToggleButton.title = WebInspector.UIString("Dock to main window.");
        }
    },

    get errors()
    {
        return this._errors || 0;
    },

    set errors(x)
    {
        x = Math.max(x, 0);

        if (this._errors === x)
            return;
        this._errors = x;
        this._updateErrorAndWarningCounts();
    },

    get warnings()
    {
        return this._warnings || 0;
    },

    set warnings(x)
    {
        x = Math.max(x, 0);

        if (this._warnings === x)
            return;
        this._warnings = x;
        this._updateErrorAndWarningCounts();
    },

    _updateErrorAndWarningCounts: function()
    {
        var errorWarningElement = document.getElementById("error-warning-count");
        if (!errorWarningElement)
            return;

        if (!this.errors && !this.warnings) {
            errorWarningElement.addStyleClass("hidden");
            return;
        }

        errorWarningElement.removeStyleClass("hidden");

        errorWarningElement.removeChildren();

        if (this.errors) {
            var errorElement = document.createElement("span");
            errorElement.id = "error-count";
            errorElement.textContent = this.errors;
            errorWarningElement.appendChild(errorElement);
        }

        if (this.warnings) {
            var warningsElement = document.createElement("span");
            warningsElement.id = "warning-count";
            warningsElement.textContent = this.warnings;
            errorWarningElement.appendChild(warningsElement);
        }

        if (this.errors) {
            if (this.warnings) {
                if (this.errors == 1) {
                    if (this.warnings == 1)
                        errorWarningElement.title = WebInspector.UIString("%d error, %d warning", this.errors, this.warnings);
                    else
                        errorWarningElement.title = WebInspector.UIString("%d error, %d warnings", this.errors, this.warnings);
                } else if (this.warnings == 1)
                    errorWarningElement.title = WebInspector.UIString("%d errors, %d warning", this.errors, this.warnings);
                else
                    errorWarningElement.title = WebInspector.UIString("%d errors, %d warnings", this.errors, this.warnings);
            } else if (this.errors == 1)
                errorWarningElement.title = WebInspector.UIString("%d error", this.errors);
            else
                errorWarningElement.title = WebInspector.UIString("%d errors", this.errors);
        } else if (this.warnings == 1)
            errorWarningElement.title = WebInspector.UIString("%d warning", this.warnings);
        else if (this.warnings)
            errorWarningElement.title = WebInspector.UIString("%d warnings", this.warnings);
        else
            errorWarningElement.title = null;
    },

    get styleChanges()
    {
        return this._styleChanges;
    },

    set styleChanges(x)
    {
        x = Math.max(x, 0);

        if (this._styleChanges === x)
            return;
        this._styleChanges = x;
        this._updateChangesCount();
    },

    _updateChangesCount: function()
    {
        // TODO: Remove immediate return when enabling the Changes Panel
        return;

        var changesElement = document.getElementById("changes-count");
        if (!changesElement)
            return;

        if (!this.styleChanges) {
            changesElement.addStyleClass("hidden");
            return;
        }

        changesElement.removeStyleClass("hidden");
        changesElement.removeChildren();

        if (this.styleChanges) {
            var styleChangesElement = document.createElement("span");
            styleChangesElement.id = "style-changes-count";
            styleChangesElement.textContent = this.styleChanges;
            changesElement.appendChild(styleChangesElement);
        }

        if (this.styleChanges) {
            if (this.styleChanges === 1)
                changesElement.title = WebInspector.UIString("%d style change", this.styleChanges);
            else
                changesElement.title = WebInspector.UIString("%d style changes", this.styleChanges);
        }
    },

    get hoveredDOMNode()
    {
        return this._hoveredDOMNode;
    },

    set hoveredDOMNode(x)
    {
        if (this._hoveredDOMNode === x)
            return;

        this._hoveredDOMNode = x;

        if (this._hoveredDOMNode)
            this._updateHoverHighlightSoon(this.showingDOMNodeHighlight ? 50 : 500);
        else
            this._updateHoverHighlight();
    },

    _updateHoverHighlightSoon: function(delay)
    {
        if ("_updateHoverHighlightTimeout" in this)
            clearTimeout(this._updateHoverHighlightTimeout);
        this._updateHoverHighlightTimeout = setTimeout(this._updateHoverHighlight.bind(this), delay);
    },

    _updateHoverHighlight: function()
    {
        if ("_updateHoverHighlightTimeout" in this) {
            clearTimeout(this._updateHoverHighlightTimeout);
            delete this._updateHoverHighlightTimeout;
        }

        if (this._hoveredDOMNode) {
            InspectorController.highlightDOMNode(this._hoveredDOMNode.id);
            this.showingDOMNodeHighlight = true;
        } else {
            InspectorController.hideDOMNodeHighlight();
            this.showingDOMNodeHighlight = false;
        }
    }
}

WebInspector.loaded = function()
{
    var platform = InspectorController.platform();
    document.body.addStyleClass("platform-" + platform);
    var port = InspectorController.port();
    document.body.addStyleClass("port-" + port);

    this._loadPreferences();
    this.pendingDispatches = 0;

    this.drawer = new WebInspector.Drawer();
    this.console = new WebInspector.ConsoleView(this.drawer);
    // TODO: Uncomment when enabling the Changes Panel
    // this.changes = new WebInspector.ChangesView(this.drawer);
    // TODO: Remove class="hidden" from inspector.html on button#changes-status-bar-item
    this.drawer.visibleView = this.console;
    this.domAgent = new WebInspector.DOMAgent();

    this.resourceCategories = {
        documents: new WebInspector.ResourceCategory("documents", WebInspector.UIString("Documents"), "rgb(47,102,236)"),
        stylesheets: new WebInspector.ResourceCategory("stylesheets", WebInspector.UIString("Stylesheets"), "rgb(157,231,119)"),
        images: new WebInspector.ResourceCategory("images", WebInspector.UIString("Images"), "rgb(164,60,255)"),
        scripts: new WebInspector.ResourceCategory("scripts", WebInspector.UIString("Scripts"), "rgb(255,121,0)"),
        xhr: new WebInspector.ResourceCategory("xhr", WebInspector.UIString("XHR"), "rgb(231,231,10)"),
        fonts: new WebInspector.ResourceCategory("fonts", WebInspector.UIString("Fonts"), "rgb(255,82,62)"),
        other: new WebInspector.ResourceCategory("other", WebInspector.UIString("Other"), "rgb(186,186,186)")
    };

    this.panels = {};
    this._createPanels();

    var toolbarElement = document.getElementById("toolbar");
    var previousToolbarItem = toolbarElement.children[0];

    this.panelOrder = [];
    for (var panelName in this.panels) {
        var panel = this.panels[panelName];
        var panelToolbarItem = panel.toolbarItem;
        this.panelOrder.push(panel);
        panelToolbarItem.addEventListener("click", this._toolbarItemClicked.bind(this));
        if (previousToolbarItem)
            toolbarElement.insertBefore(panelToolbarItem, previousToolbarItem.nextSibling);
        else
            toolbarElement.insertBefore(panelToolbarItem, toolbarElement.firstChild);
        previousToolbarItem = panelToolbarItem;
    }

    this.Tips = {
        ResourceNotCompressed: {id: 0, message: WebInspector.UIString("You could save bandwidth by having your web server compress this transfer with gzip or zlib.")}
    };

    this.Warnings = {
        IncorrectMIMEType: {id: 0, message: WebInspector.UIString("Resource interpreted as %s but transferred with MIME type %s.")}
    };

    this.addMainEventListeners(document);

    window.addEventListener("unload", this.windowUnload.bind(this), true);
    window.addEventListener("resize", this.windowResize.bind(this), true);

    document.addEventListener("focus", this.focusChanged.bind(this), true);
    document.addEventListener("keydown", this.documentKeyDown.bind(this), true);
    document.addEventListener("keyup", this.documentKeyUp.bind(this), true);
    document.addEventListener("beforecopy", this.documentCanCopy.bind(this), true);
    document.addEventListener("copy", this.documentCopy.bind(this), true);
    document.addEventListener("contextmenu", this.contextMenu.bind(this), true);

    var mainPanelsElement = document.getElementById("main-panels");
    mainPanelsElement.handleKeyEvent = this.mainKeyDown.bind(this);
    mainPanelsElement.handleKeyUpEvent = this.mainKeyUp.bind(this);
    mainPanelsElement.handleCopyEvent = this.mainCopy.bind(this);

    // Focus the mainPanelsElement in a timeout so it happens after the initial focus,
    // so it doesn't get reset to the first toolbar button. This initial focus happens
    // on Mac when the window is made key and the WebHTMLView becomes the first responder.
    setTimeout(function() { WebInspector.currentFocusElement = mainPanelsElement }, 0);

    var dockToggleButton = document.getElementById("dock-status-bar-item");
    dockToggleButton.addEventListener("click", this.toggleAttach.bind(this), false);

    if (this.attached)
        dockToggleButton.title = WebInspector.UIString("Undock into separate window.");
    else
        dockToggleButton.title = WebInspector.UIString("Dock to main window.");

    var errorWarningCount = document.getElementById("error-warning-count");
    errorWarningCount.addEventListener("click", this.showConsole.bind(this), false);
    this._updateErrorAndWarningCounts();

    this.styleChanges = 0;
    // TODO: Uncomment when enabling the Changes Panel
    // var changesElement = document.getElementById("changes-count");
    // changesElement.addEventListener("click", this.showChanges.bind(this), false);
    // this._updateErrorAndWarningCounts();

    var searchField = document.getElementById("search");
    searchField.addEventListener("search", this.performSearch.bind(this), false); // when the search is emptied

    toolbarElement.addEventListener("mousedown", this.toolbarDragStart, true);
    document.getElementById("close-button-left").addEventListener("click", this.close, true);
    document.getElementById("close-button-right").addEventListener("click", this.close, true);

    InspectorController.loaded();
}

var windowLoaded = function()
{
    var localizedStringsURL = InspectorController.localizedStringsURL();
    if (localizedStringsURL) {
        var localizedStringsScriptElement = document.createElement("script");
        localizedStringsScriptElement.addEventListener("load", WebInspector.loaded.bind(WebInspector), false);
        localizedStringsScriptElement.type = "text/javascript";
        localizedStringsScriptElement.src = localizedStringsURL;
        document.getElementsByTagName("head").item(0).appendChild(localizedStringsScriptElement);
    } else
        WebInspector.loaded();

    window.removeEventListener("load", windowLoaded, false);
    delete windowLoaded;
};

window.addEventListener("load", windowLoaded, false);

WebInspector.dispatch = function() {
    var methodName = arguments[0];
    var parameters = Array.prototype.slice.call(arguments, 1);

    // We'd like to enforce asynchronous interaction between the inspector controller and the frontend.
    // This is important to LayoutTests.
    function delayDispatch()
    {
        WebInspector[methodName].apply(WebInspector, parameters);
        WebInspector.pendingDispatches--;
    }
    WebInspector.pendingDispatches++;
    setTimeout(delayDispatch, 0);
}

WebInspector.windowUnload = function(event)
{
    InspectorController.windowUnloading();
}

WebInspector.windowResize = function(event)
{
    if (this.currentPanel && this.currentPanel.resize)
        this.currentPanel.resize();
}

WebInspector.windowFocused = function(event)
{
    // Fires after blur, so when focusing on either the main inspector
    // or an <iframe> within the inspector we should always remove the
    // "inactive" class.
    if (event.target.document.nodeType === Node.DOCUMENT_NODE)
        document.body.removeStyleClass("inactive");
}

WebInspector.windowBlurred = function(event)
{
    // Leaving the main inspector or an <iframe> within the inspector.
    // We can add "inactive" now, and if we are moving the focus to another
    // part of the inspector then windowFocused will correct this.
    if (event.target.document.nodeType === Node.DOCUMENT_NODE)
        document.body.addStyleClass("inactive");
}

WebInspector.focusChanged = function(event)
{
    this.currentFocusElement = event.target;
}

WebInspector.setAttachedWindow = function(attached)
{
    this.attached = attached;
}

WebInspector.close = function(event)
{
    InspectorController.closeWindow();
}

WebInspector.documentClick = function(event)
{
    var anchor = event.target.enclosingNodeOrSelfWithNodeName("a");
    if (!anchor)
        return;

    // Prevent the link from navigating, since we don't do any navigation by following links normally.
    event.preventDefault();

    function followLink()
    {
        // FIXME: support webkit-html-external-link links here.
        if (anchor.href in WebInspector.resourceURLMap) {
            if (anchor.hasStyleClass("webkit-html-external-link")) {
                anchor.removeStyleClass("webkit-html-external-link");
                anchor.addStyleClass("webkit-html-resource-link");
            }

            WebInspector.showResourceForURL(anchor.href, anchor.lineNumber, anchor.preferredPanel);
        } else {
            var profileString = WebInspector.ProfileType.URLRegExp.exec(anchor.href);
            if (profileString)
                WebInspector.showProfileForURL(anchor.href);
        }
    }

    if (WebInspector.followLinkTimeout)
        clearTimeout(WebInspector.followLinkTimeout);

    if (anchor.preventFollowOnDoubleClick) {
        // Start a timeout if this is the first click, if the timeout is canceled
        // before it fires, then a double clicked happened or another link was clicked.
        if (event.detail === 1)
            WebInspector.followLinkTimeout = setTimeout(followLink, 333);
        return;
    }

    followLink();
}

WebInspector.documentKeyDown = function(event)
{
    if (!this.currentFocusElement)
        return;
    if (this.currentFocusElement.handleKeyEvent)
        this.currentFocusElement.handleKeyEvent(event);
    else if (this.currentFocusElement.id && this.currentFocusElement.id.length && WebInspector[this.currentFocusElement.id + "KeyDown"])
        WebInspector[this.currentFocusElement.id + "KeyDown"](event);

    if (!event.handled) {
        var isMac = InspectorController.platform().indexOf("mac-") === 0;

        switch (event.keyIdentifier) {
            case "U+001B": // Escape key
                this.drawer.visible = !this.drawer.visible;
                event.preventDefault();
                break;

            case "U+0046": // F key
                if (isMac)
                    var isFindKey = event.metaKey && !event.ctrlKey && !event.altKey && !event.shiftKey;
                else
                    var isFindKey = event.ctrlKey && !event.metaKey && !event.altKey && !event.shiftKey;

                if (isFindKey) {
                    var searchField = document.getElementById("search");
                    searchField.focus();
                    searchField.select();
                    event.preventDefault();
                }

                break;

            case "U+0047": // G key
                if (isMac)
                    var isFindAgainKey = event.metaKey && !event.ctrlKey && !event.altKey;
                else
                    var isFindAgainKey = event.ctrlKey && !event.metaKey && !event.altKey;

                if (isFindAgainKey) {
                    if (event.shiftKey) {
                        if (this.currentPanel.jumpToPreviousSearchResult)
                            this.currentPanel.jumpToPreviousSearchResult();
                    } else if (this.currentPanel.jumpToNextSearchResult)
                        this.currentPanel.jumpToNextSearchResult();
                    event.preventDefault();
                }

                break;

            // Windows and Mac have two different definitions of [, so accept both.
            case "U+005B":
            case "U+00DB": // [ key
                if (isMac)
                    var isRotateLeft = event.metaKey && !event.shiftKey && !event.ctrlKey && !event.altKey;
                else
                    var isRotateLeft = event.ctrlKey && !event.shiftKey && !event.metaKey && !event.altKey;

                if (isRotateLeft) {
                    var index = this.panelOrder.indexOf(this.currentPanel);
                    index = (index === 0) ? this.panelOrder.length - 1 : index - 1;
                    this.panelOrder[index].toolbarItem.click();
                    event.preventDefault();
                }

                break;

            // Windows and Mac have two different definitions of ], so accept both.
            case "U+005D":
            case "U+00DD":  // ] key
                if (isMac)
                    var isRotateRight = event.metaKey && !event.shiftKey && !event.ctrlKey && !event.altKey;
                else
                    var isRotateRight = event.ctrlKey && !event.shiftKey && !event.metaKey && !event.altKey;

                if (isRotateRight) {
                    var index = this.panelOrder.indexOf(this.currentPanel);
                    index = (index + 1) % this.panelOrder.length;
                    this.panelOrder[index].toolbarItem.click();
                    event.preventDefault();
                }

                break;
        }
    }
}

WebInspector.documentKeyUp = function(event)
{
    if (!this.currentFocusElement || !this.currentFocusElement.handleKeyUpEvent)
        return;
    this.currentFocusElement.handleKeyUpEvent(event);
}

WebInspector.documentCanCopy = function(event)
{
    if (!this.currentFocusElement)
        return;
    // Calling preventDefault() will say "we support copying, so enable the Copy menu".
    if (this.currentFocusElement.handleCopyEvent)
        event.preventDefault();
    else if (this.currentFocusElement.id && this.currentFocusElement.id.length && WebInspector[this.currentFocusElement.id + "Copy"])
        event.preventDefault();
}

WebInspector.documentCopy = function(event)
{
    if (!this.currentFocusElement)
        return;
    if (this.currentFocusElement.handleCopyEvent)
        this.currentFocusElement.handleCopyEvent(event);
    else if (this.currentFocusElement.id && this.currentFocusElement.id.length && WebInspector[this.currentFocusElement.id + "Copy"])
        WebInspector[this.currentFocusElement.id + "Copy"](event);
}

WebInspector.contextMenu = function(event)
{
    if (event.handled || event.target.hasStyleClass("popup-glasspane"))
        event.preventDefault();
}

WebInspector.mainKeyDown = function(event)
{
    if (this.currentPanel && this.currentPanel.handleKeyEvent)
        this.currentPanel.handleKeyEvent(event);
}

WebInspector.mainKeyUp = function(event)
{
    if (this.currentPanel && this.currentPanel.handleKeyUpEvent)
        this.currentPanel.handleKeyUpEvent(event);
}

WebInspector.mainCopy = function(event)
{
    if (this.currentPanel && this.currentPanel.handleCopyEvent)
        this.currentPanel.handleCopyEvent(event);
}

WebInspector.animateStyle = function(animations, duration, callback, complete)
{
    if (complete === undefined)
        complete = 0;
    var slice = (1000 / 30); // 30 frames per second

    var defaultUnit = "px";
    var propertyUnit = {opacity: ""};

    for (var i = 0; i < animations.length; ++i) {
        var animation = animations[i];
        var element = null;
        var start = null;
        var current = null;
        var end = null;
        var key = null;
        for (key in animation) {
            if (key === "element")
                element = animation[key];
            else if (key === "start")
                start = animation[key];
            else if (key === "current")
                current = animation[key];
            else if (key === "end")
                end = animation[key];
        }

        if (!element || !end)
            continue;

        var computedStyle = element.ownerDocument.defaultView.getComputedStyle(element);
        if (!start) {
            start = {};
            for (key in end)
                start[key] = parseInt(computedStyle.getPropertyValue(key));
            animation.start = start;
        } else if (complete == 0)
            for (key in start)
                element.style.setProperty(key, start[key] + (key in propertyUnit ? propertyUnit[key] : defaultUnit));

        if (!current) {
            current = {};
            for (key in start)
                current[key] = start[key];
            animation.current = current;
        }

        function cubicInOut(t, b, c, d)
        {
            if ((t/=d/2) < 1) return c/2*t*t*t + b;
            return c/2*((t-=2)*t*t + 2) + b;
        }

        var style = element.style;
        for (key in end) {
            var startValue = start[key];
            var currentValue = current[key];
            var endValue = end[key];
            if ((complete + slice) < duration) {
                var delta = (endValue - startValue) / (duration / slice);
                var newValue = cubicInOut(complete, startValue, endValue - startValue, duration);
                style.setProperty(key, newValue + (key in propertyUnit ? propertyUnit[key] : defaultUnit));
                current[key] = newValue;
            } else {
                style.setProperty(key, endValue + (key in propertyUnit ? propertyUnit[key] : defaultUnit));
            }
        }
    }

    if (complete < duration)
        setTimeout(WebInspector.animateStyle, slice, animations, duration, callback, complete + slice);
    else if (callback)
        callback();
}

WebInspector.updateSearchLabel = function()
{
    if (!this.currentPanel)
        return;

    var newLabel = WebInspector.UIString("Search %s", this.currentPanel.toolbarItemLabel);
    if (this.attached)
        document.getElementById("search").setAttribute("placeholder", newLabel);
    else {
        document.getElementById("search").removeAttribute("placeholder");
        document.getElementById("search-toolbar-label").textContent = newLabel;
    }
}

WebInspector.toggleAttach = function()
{
    this.attached = !this.attached;
}

WebInspector.toolbarDragStart = function(event)
{
    if ((!WebInspector.attached && InspectorController.platform() !== "mac-leopard") || InspectorController.port() == "qt")
        return;

    var target = event.target;
    if (target.hasStyleClass("toolbar-item") && target.hasStyleClass("toggleable"))
        return;

    var toolbar = document.getElementById("toolbar");
    if (target !== toolbar && !target.hasStyleClass("toolbar-item"))
        return;

    toolbar.lastScreenX = event.screenX;
    toolbar.lastScreenY = event.screenY;

    WebInspector.elementDragStart(toolbar, WebInspector.toolbarDrag, WebInspector.toolbarDragEnd, event, (WebInspector.attached ? "row-resize" : "default"));
}

WebInspector.toolbarDragEnd = function(event)
{
    var toolbar = document.getElementById("toolbar");

    WebInspector.elementDragEnd(event);

    delete toolbar.lastScreenX;
    delete toolbar.lastScreenY;
}

WebInspector.toolbarDrag = function(event)
{
    var toolbar = document.getElementById("toolbar");

    if (WebInspector.attached) {
        var height = window.innerHeight - (event.screenY - toolbar.lastScreenY);

        InspectorController.setAttachedWindowHeight(height);
    } else {
        var x = event.screenX - toolbar.lastScreenX;
        var y = event.screenY - toolbar.lastScreenY;

        // We cannot call window.moveBy here because it restricts the movement
        // of the window at the edges.
        InspectorController.moveByUnrestricted(x, y);
    }

    toolbar.lastScreenX = event.screenX;
    toolbar.lastScreenY = event.screenY;

    event.preventDefault();
}

WebInspector.elementDragStart = function(element, dividerDrag, elementDragEnd, event, cursor)
{
    if (this._elementDraggingEventListener || this._elementEndDraggingEventListener)
        this.elementDragEnd(event);

    this._elementDraggingEventListener = dividerDrag;
    this._elementEndDraggingEventListener = elementDragEnd;

    document.addEventListener("mousemove", dividerDrag, true);
    document.addEventListener("mouseup", elementDragEnd, true);

    document.body.style.cursor = cursor;

    event.preventDefault();
}

WebInspector.elementDragEnd = function(event)
{
    document.removeEventListener("mousemove", this._elementDraggingEventListener, true);
    document.removeEventListener("mouseup", this._elementEndDraggingEventListener, true);

    document.body.style.removeProperty("cursor");

    delete this._elementDraggingEventListener;
    delete this._elementEndDraggingEventListener;

    event.preventDefault();
}

WebInspector.showConsole = function()
{
    this.drawer.showView(this.console);
}

WebInspector.showChanges = function()
{
    this.drawer.showView(this.changes);
}

WebInspector.showElementsPanel = function()
{
    this.currentPanel = this.panels.elements;
}

WebInspector.showResourcesPanel = function()
{
    this.currentPanel = this.panels.resources;
}

WebInspector.showScriptsPanel = function()
{
    this.currentPanel = this.panels.scripts;
}

WebInspector.showTimelinePanel = function()
{
    this.currentPanel = this.panels.timeline;
}

WebInspector.showProfilesPanel = function()
{
    this.currentPanel = this.panels.profiles;
}

WebInspector.showStoragePanel = function()
{
    this.currentPanel = this.panels.storage;
}

WebInspector.addResource = function(identifier, payload)
{
    var resource = new WebInspector.Resource(
        payload.requestHeaders,
        payload.requestURL,
        payload.host,
        payload.path,
        payload.lastPathComponent,
        identifier,
        payload.isMainResource,
        payload.cached,
        payload.requestMethod,
        payload.requestFormData);
    this.resources[identifier] = resource;
    this.resourceURLMap[resource.url] = resource;

    if (resource.mainResource)
        this.mainResource = resource;

    if (this.panels.resources)
        this.panels.resources.addResource(resource);
}

WebInspector.clearConsoleMessages = function()
{
    WebInspector.console.clearMessages(false);
}

WebInspector.selectDatabase = function(o)
{
    WebInspector.showStoragePanel();
    WebInspector.panels.storage.selectDatabase(o);
}

WebInspector.selectDOMStorage = function(o)
{
    WebInspector.showStoragePanel();
    WebInspector.panels.storage.selectDOMStorage(o);
}

WebInspector.updateResource = function(identifier, payload)
{
    var resource = this.resources[identifier];
    if (!resource)
        return;

    if (payload.didRequestChange) {
        resource.url = payload.url;
        resource.domain = payload.domain;
        resource.path = payload.path;
        resource.lastPathComponent = payload.lastPathComponent;
        resource.requestHeaders = payload.requestHeaders;
        resource.mainResource = payload.mainResource;
        resource.requestMethod = payload.requestMethod;
        resource.requestFormData = payload.requestFormData;
    }

    if (payload.didResponseChange) {
        resource.mimeType = payload.mimeType;
        resource.suggestedFilename = payload.suggestedFilename;
        resource.expectedContentLength = payload.expectedContentLength;
        resource.statusCode = payload.statusCode;
        resource.suggestedFilename = payload.suggestedFilename;
        resource.responseHeaders = payload.responseHeaders;
    }

    if (payload.didTypeChange) {
        resource.type = payload.type;
    }

    if (payload.didLengthChange) {
        resource.contentLength = payload.contentLength;
    }

    if (payload.didCompletionChange) {
        resource.failed = payload.failed;
        resource.finished = payload.finished;
    }

    if (payload.didTimingChange) {
        if (payload.startTime)
            resource.startTime = payload.startTime;
        if (payload.responseReceivedTime)
            resource.responseReceivedTime = payload.responseReceivedTime;
        if (payload.endTime)
            resource.endTime = payload.endTime;
        
        if (payload.loadEventTime) {
            // This loadEventTime is for the main resource, and we want to show it
            // for all resources on this page. This means we want to set it as a member
            // of the resources panel instead of the individual resource.
            if (this.panels.resources)
                this.panels.resources.mainResourceLoadTime = payload.loadEventTime;
        }
        
        if (payload.domContentEventTime) {
            // This domContentEventTime is for the main resource, so it should go in
            // the resources panel for the same reasons as above.
            if (this.panels.resources)
                this.panels.resources.mainResourceDOMContentTime = payload.domContentEventTime;
        }
    }
}

WebInspector.removeResource = function(identifier)
{
    var resource = this.resources[identifier];
    if (!resource)
        return;

    resource.category.removeResource(resource);
    delete this.resourceURLMap[resource.url];
    delete this.resources[identifier];

    if (this.panels.resources)
        this.panels.resources.removeResource(resource);
}

WebInspector.addDatabase = function(payload)
{
    if (!this.panels.storage)
        return;
    var database = new WebInspector.Database(
        payload.id,
        payload.domain,
        payload.name,
        payload.version);
    this.panels.storage.addDatabase(database);
}

WebInspector.addCookieDomain = function(domain)
{
    if (!this.panels.storage)
        return;
    this.panels.storage.addCookieDomain(domain);
}

WebInspector.addDOMStorage = function(payload)
{
    if (!this.panels.storage)
        return;
    var domStorage = new WebInspector.DOMStorage(
        payload.id,
        payload.host,
        payload.isLocalStorage);
    this.panels.storage.addDOMStorage(domStorage);
}

WebInspector.updateDOMStorage = function(storageId)
{
    if (!this.panels.storage)
        return;
    this.panels.storage.updateDOMStorage(storageId);
}

WebInspector.resourceTrackingWasEnabled = function()
{
    this.panels.resources.resourceTrackingWasEnabled();
}

WebInspector.resourceTrackingWasDisabled = function()
{
    this.panels.resources.resourceTrackingWasDisabled();
}

WebInspector.attachDebuggerWhenShown = function()
{
    this.panels.scripts.attachDebuggerWhenShown();
}

WebInspector.debuggerWasEnabled = function()
{
    this.panels.scripts.debuggerWasEnabled();
}

WebInspector.debuggerWasDisabled = function()
{
    this.panels.scripts.debuggerWasDisabled();
}

WebInspector.profilerWasEnabled = function()
{
    this.panels.profiles.profilerWasEnabled();
}

WebInspector.profilerWasDisabled = function()
{
    this.panels.profiles.profilerWasDisabled();
}

WebInspector.parsedScriptSource = function(sourceID, sourceURL, source, startingLine)
{
    this.panels.scripts.addScript(sourceID, sourceURL, source, startingLine);
}

WebInspector.failedToParseScriptSource = function(sourceURL, source, startingLine, errorLine, errorMessage)
{
    this.panels.scripts.addScript(null, sourceURL, source, startingLine, errorLine, errorMessage);
}

WebInspector.pausedScript = function(callFrames)
{
    this.panels.scripts.debuggerPaused(callFrames);
}

WebInspector.resumedScript = function()
{
    this.panels.scripts.debuggerResumed();
}

WebInspector.populateInterface = function()
{
    for (var panelName in this.panels) {
        var panel = this.panels[panelName];
        if ("populateInterface" in panel)
            panel.populateInterface();
    }
}

WebInspector.reset = function()
{
    for (var panelName in this.panels) {
        var panel = this.panels[panelName];
        if ("reset" in panel)
            panel.reset();
    }

    for (var category in this.resourceCategories)
        this.resourceCategories[category].removeAllResources();

    this.resources = {};
    this.resourceURLMap = {};
    this.hoveredDOMNode = null;

    delete this.mainResource;

    this.console.clearMessages();
}

WebInspector.resourceURLChanged = function(resource, oldURL)
{
    delete this.resourceURLMap[oldURL];
    this.resourceURLMap[resource.url] = resource;
}

WebInspector.didCommitLoad = function()
{
    // Cleanup elements panel early on inspected page refresh.
    WebInspector.setDocument(null);
}

WebInspector.updateConsoleMessageExpiredCount = function(count)
{
    var message = String.sprintf(WebInspector.UIString("%d console messages are not shown."), count);
    WebInspector.console.addMessage(new WebInspector.ConsoleTextMessage(message, WebInspector.ConsoleMessage.MessageLevel.Warning));
}

WebInspector.addConsoleMessage = function(payload)
{
    var consoleMessage = new WebInspector.ConsoleMessage(
        payload.source,
        payload.type,
        payload.level,
        payload.line,
        payload.url,
        payload.groupLevel,
        payload.repeatCount);
    consoleMessage.setMessageBody(Array.prototype.slice.call(arguments, 1));
    this.console.addMessage(consoleMessage);
}

WebInspector.updateConsoleMessageRepeatCount = function(count)
{
    this.console.updateMessageRepeatCount(count);
}

WebInspector.log = function(message)
{
    // remember 'this' for setInterval() callback
    var self = this;
    
    // return indication if we can actually log a message
    function isLogAvailable()
    {
        return WebInspector.ConsoleMessage && WebInspector.ObjectProxy && self.console;
    }
    
    // flush the queue of pending messages
    function flushQueue()
    {
        var queued = WebInspector.log.queued;
        if (!queued) 
            return;
            
        for (var i = 0; i < queued.length; ++i)
            logMessage(queued[i]);
        
        delete WebInspector.log.queued;
    }

    // flush the queue if it console is available
    // - this function is run on an interval
    function flushQueueIfAvailable()
    {
        if (!isLogAvailable())
            return;
            
        clearInterval(WebInspector.log.interval);
        delete WebInspector.log.interval;
        
        flushQueue();
    }
    
    // actually log the message
    function logMessage(message)
    {
        var repeatCount = 1;
        if (message == WebInspector.log.lastMessage)
            repeatCount = WebInspector.log.repeatCount + 1;
    
        WebInspector.log.lastMessage = message;
        WebInspector.log.repeatCount = repeatCount;
        
        // ConsoleMessage expects a proxy object
        message = new WebInspector.ObjectProxy(null, [], 0, message, false);
        
        // post the message
        var msg = new WebInspector.ConsoleMessage(
            WebInspector.ConsoleMessage.MessageSource.Other,
            WebInspector.ConsoleMessage.MessageType.Log,
            WebInspector.ConsoleMessage.MessageLevel.Debug,
            -1,
            null,
            null,
            repeatCount,
            message);
    
        self.console.addMessage(msg);
    }
    
    // if we can't log the message, queue it
    if (!isLogAvailable()) {
        if (!WebInspector.log.queued)
            WebInspector.log.queued = [];
            
        WebInspector.log.queued.push(message);
        
        if (!WebInspector.log.interval)
            WebInspector.log.interval = setInterval(flushQueueIfAvailable, 1000);
        
        return;
    }

    // flush the pending queue if any
    flushQueue();

    // log the message
    logMessage(message);
}

WebInspector.addProfileHeader = function(profile)
{
    this.panels.profiles.addProfileHeader(profile);
}

WebInspector.setRecordingProfile = function(isProfiling)
{
    this.panels.profiles.getProfileType(WebInspector.CPUProfileType.TypeId).setRecordingProfile(isProfiling);
    this.panels.profiles.updateProfileTypeButtons();
}

WebInspector.drawLoadingPieChart = function(canvas, percent) {
    var g = canvas.getContext("2d");
    var darkColor = "rgb(122, 168, 218)";
    var lightColor = "rgb(228, 241, 251)";
    var cx = 8;
    var cy = 8;
    var r = 7;

    g.beginPath();
    g.arc(cx, cy, r, 0, Math.PI * 2, false);
    g.closePath();

    g.lineWidth = 1;
    g.strokeStyle = darkColor;
    g.fillStyle = lightColor;
    g.fill();
    g.stroke();

    var startangle = -Math.PI / 2;
    var endangle = startangle + (percent * Math.PI * 2);

    g.beginPath();
    g.moveTo(cx, cy);
    g.arc(cx, cy, r, startangle, endangle, false);
    g.closePath();

    g.fillStyle = darkColor;
    g.fill();
}

WebInspector.updateFocusedNode = function(nodeId)
{
    var node = WebInspector.domAgent.nodeForId(nodeId);
    if (!node)
        // FIXME: Should we deselect if null is passed in?
        return;

    this.currentPanel = this.panels.elements;
    this.panels.elements.focusedDOMNode = node;
}

WebInspector.displayNameForURL = function(url)
{
    if (!url)
        return "";
    var resource = this.resourceURLMap[url];
    if (resource)
        return resource.displayName;
    return url.trimURL(WebInspector.mainResource ? WebInspector.mainResource.domain : "");
}

WebInspector.resourceForURL = function(url)
{
    if (url in this.resourceURLMap)
        return this.resourceURLMap[url];

    // No direct match found. Search for resources that contain
    // a substring of the URL.
    for (var resourceURL in this.resourceURLMap) {
        if (resourceURL.hasSubstring(url))
            return this.resourceURLMap[resourceURL];
    }

    return null;
}

WebInspector.showResourceForURL = function(url, line, preferredPanel)
{
    var resource = this.resourceForURL(url);
    if (!resource)
        return false;

    if (preferredPanel && preferredPanel in WebInspector.panels) {
        var panel = this.panels[preferredPanel];
        if (!("showResource" in panel))
            panel = null;
        else if ("canShowResource" in panel && !panel.canShowResource(resource))
            panel = null;
    }

    this.currentPanel = panel || this.panels.resources;
    if (!this.currentPanel)
        return false;
    this.currentPanel.showResource(resource, line);
    return true;
}

WebInspector.linkifyStringAsFragment = function(string)
{
    var container = document.createDocumentFragment();
    var linkStringRegEx = new RegExp("(?:[a-zA-Z][a-zA-Z0-9+.-]{2,}://|www\\.)[\\w$\\-_+*'=\\|/\\\\(){}[\\]%@&#~,:;.!?]{2,}[\\w$\\-_+*=\\|/\\\\({%@&#~]");

    while (string) {
        var linkString = linkStringRegEx.exec(string);
        if (!linkString)
            break;

        linkString = linkString[0];
        var title = linkString;
        var linkIndex = string.indexOf(linkString);
        var nonLink = string.substring(0, linkIndex);
        container.appendChild(document.createTextNode(nonLink));

        var profileStringMatches = WebInspector.ProfileType.URLRegExp.exec(title);
        if (profileStringMatches)
            title = WebInspector.panels.profiles.displayTitleForProfileLink(profileStringMatches[2], profileStringMatches[1]);

        var realURL = (linkString.indexOf("www.") === 0 ? "http://" + linkString : linkString);
        container.appendChild(WebInspector.linkifyURLAsNode(realURL, title, null, (realURL in WebInspector.resourceURLMap)));
        string = string.substring(linkIndex + linkString.length, string.length);
    }

    if (string)
        container.appendChild(document.createTextNode(string));

    return container;
}

WebInspector.showProfileForURL = function(url) {
    WebInspector.showProfilesPanel();
    WebInspector.panels.profiles.showProfileForURL(url);
}

WebInspector.linkifyURLAsNode = function(url, linkText, classes, isExternal)
{
    if (!linkText)
        linkText = url;
    classes = (classes ? classes + " " : "");
    classes += isExternal ? "webkit-html-external-link" : "webkit-html-resource-link";

    var a = document.createElement("a");
    a.href = url;
    a.className = classes;
    a.title = url;
    a.target = "_blank";
    a.textContent = linkText;

    return a;
}

WebInspector.linkifyURL = function(url, linkText, classes, isExternal)
{
    // Use the DOM version of this function so as to avoid needing to escape attributes.
    // FIXME:  Get rid of linkifyURL entirely.
    return WebInspector.linkifyURLAsNode(url, linkText, classes, isExternal).outerHTML;
}

WebInspector.addMainEventListeners = function(doc)
{
    doc.defaultView.addEventListener("focus", this.windowFocused.bind(this), false);
    doc.defaultView.addEventListener("blur", this.windowBlurred.bind(this), false);
    doc.addEventListener("click", this.documentClick.bind(this), true);
}

WebInspector.searchKeyDown = function(event)
{
    if (!isEnterKey(event))
        return false;

    // Select all of the text so the user can easily type an entirely new query.
    event.target.select();

    // Only call performSearch if the Enter key was pressed. Otherwise the search
    // performance is poor because of searching on every key. The search field has
    // the incremental attribute set, so we still get incremental searches.
    this.performSearch(event);

    // Call preventDefault since this was the Enter key. This prevents a "search" event
    // from firing for key down. This stops performSearch from being called twice in a row.
    event.preventDefault();
}

WebInspector.performSearch = function(event)
{
    var query = event.target.value;
    var forceSearch = event.keyIdentifier === "Enter";
    var isShortSearch = (query.length < 3);

    // Clear a leftover short search flag due to a non-conflicting forced search.
    if (isShortSearch && this.shortSearchWasForcedByKeyEvent && this.currentQuery !== query)
        delete this.shortSearchWasForcedByKeyEvent;

    // Indicate this was a forced search on a short query.
    if (isShortSearch && forceSearch)
        this.shortSearchWasForcedByKeyEvent = true;

    if (!query || !query.length || (!forceSearch && isShortSearch)) {
        // Prevent clobbering a short search forced by the user.
        if (this.shortSearchWasForcedByKeyEvent) {
            delete this.shortSearchWasForcedByKeyEvent;
            return;
        }

        delete this.currentQuery;

        for (var panelName in this.panels) {
            var panel = this.panels[panelName];
            if (panel.currentQuery && panel.searchCanceled)
                panel.searchCanceled();
            delete panel.currentQuery;
        }

        this.updateSearchMatchesCount();

        return;
    }

    if (query === this.currentPanel.currentQuery && this.currentPanel.currentQuery === this.currentQuery) {
        // When this is the same query and a forced search, jump to the next
        // search result for a good user experience.
        if (forceSearch && this.currentPanel.jumpToNextSearchResult)
            this.currentPanel.jumpToNextSearchResult();
        return;
    }

    this.currentQuery = query;

    this.updateSearchMatchesCount();

    if (!this.currentPanel.performSearch)
        return;

    this.currentPanel.currentQuery = query;
    this.currentPanel.performSearch(query);
}

WebInspector.addNodesToSearchResult = function(nodeIds)
{
    WebInspector.panels.elements.addNodesToSearchResult(nodeIds);
}

WebInspector.updateSearchMatchesCount = function(matches, panel)
{
    if (!panel)
        panel = this.currentPanel;

    panel.currentSearchMatches = matches;

    if (panel !== this.currentPanel)
        return;

    if (!this.currentPanel.currentQuery) {
        document.getElementById("search-results-matches").addStyleClass("hidden");
        return;
    }

    if (matches) {
        if (matches === 1)
            var matchesString = WebInspector.UIString("1 match");
        else
            var matchesString = WebInspector.UIString("%d matches", matches);
    } else
        var matchesString = WebInspector.UIString("Not Found");

    var matchesToolbarElement = document.getElementById("search-results-matches");
    matchesToolbarElement.removeStyleClass("hidden");
    matchesToolbarElement.textContent = matchesString;
}

WebInspector.UIString = function(string)
{
    if (window.localizedStrings && string in window.localizedStrings)
        string = window.localizedStrings[string];
    else {
        if (!(string in this.missingLocalizedStrings)) {
            if (!WebInspector.InspectorControllerStub)
                console.error("Localized string \"" + string + "\" not found.");
            this.missingLocalizedStrings[string] = true;
        }

        if (Preferences.showMissingLocalizedStrings)
            string += " (not localized)";
    }

    return String.vsprintf(string, Array.prototype.slice.call(arguments, 1));
}

WebInspector.isBeingEdited = function(element)
{
    return element.__editing;
}

WebInspector.startEditing = function(element, committedCallback, cancelledCallback, context)
{
    if (element.__editing)
        return;
    element.__editing = true;

    var oldText = getContent(element);
    var oldHandleKeyEvent = element.handleKeyEvent;
    var moveDirection = "";

    element.addStyleClass("editing");

    var oldTabIndex = element.tabIndex;
    if (element.tabIndex < 0)
        element.tabIndex = 0;

    function blurEventListener() {
        editingCommitted.call(element);
    }

    function getContent(element) {
        if (element.tagName === "INPUT" && element.type === "text")
            return element.value;
        else
            return element.textContent;
    }

    function cleanUpAfterEditing() {
        delete this.__editing;

        this.removeStyleClass("editing");
        this.tabIndex = oldTabIndex;
        this.scrollTop = 0;
        this.scrollLeft = 0;

        this.handleKeyEvent = oldHandleKeyEvent;
        element.removeEventListener("blur", blurEventListener, false);

        if (element === WebInspector.currentFocusElement || element.isAncestor(WebInspector.currentFocusElement))
            WebInspector.currentFocusElement = WebInspector.previousFocusElement;
    }

    function editingCancelled() {
        if (this.tagName === "INPUT" && this.type === "text")
            this.value = oldText;
        else
            this.textContent = oldText;

        cleanUpAfterEditing.call(this);

        if (cancelledCallback)
            cancelledCallback(this, context);
    }

    function editingCommitted() {
        cleanUpAfterEditing.call(this);

        if (committedCallback)
            committedCallback(this, getContent(this), oldText, context, moveDirection);
    }

    element.handleKeyEvent = function(event) {
        if (oldHandleKeyEvent)
            oldHandleKeyEvent(event);
        if (event.handled)
            return;

        if (isEnterKey(event)) {
            editingCommitted.call(element);
            event.preventDefault();
        } else if (event.keyCode === 27) { // Escape key
            editingCancelled.call(element);
            event.preventDefault();
            event.handled = true;
        } else if (event.keyIdentifier === "U+0009") // Tab key
            moveDirection = (event.shiftKey ? "backward" : "forward");
    }

    element.addEventListener("blur", blurEventListener, false);

    WebInspector.currentFocusElement = element;
}

WebInspector._toolbarItemClicked = function(event)
{
    var toolbarItem = event.currentTarget;
    this.currentPanel = toolbarItem.panel;
}

// This table maps MIME types to the Resource.Types which are valid for them.
// The following line:
//    "text/html":                {0: 1},
// means that text/html is a valid MIME type for resources that have type
// WebInspector.Resource.Type.Document (which has a value of 0).
WebInspector.MIMETypes = {
    "text/html":                   {0: true},
    "text/xml":                    {0: true},
    "text/plain":                  {0: true},
    "application/xhtml+xml":       {0: true},
    "text/css":                    {1: true},
    "text/xsl":                    {1: true},
    "image/jpeg":                  {2: true},
    "image/png":                   {2: true},
    "image/gif":                   {2: true},
    "image/bmp":                   {2: true},
    "image/vnd.microsoft.icon":    {2: true},
    "image/x-icon":                {2: true},
    "image/x-xbitmap":             {2: true},
    "font/ttf":                    {3: true},
    "font/opentype":               {3: true},
    "application/x-font-type1":    {3: true},
    "application/x-font-ttf":      {3: true},
    "application/x-truetype-font": {3: true},
    "text/javascript":             {4: true},
    "text/ecmascript":             {4: true},
    "application/javascript":      {4: true},
    "application/ecmascript":      {4: true},
    "application/x-javascript":    {4: true},
    "text/javascript1.1":          {4: true},
    "text/javascript1.2":          {4: true},
    "text/javascript1.3":          {4: true},
    "text/jscript":                {4: true},
    "text/livescript":             {4: true},
}

/* InspectorControllerStub.js */

/*
 * Copyright (C) 2009 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

if (!window.InspectorController) {

WebInspector.InspectorControllerStub = function()
{
    this._searchingForNode = false;
    this._windowVisible = true;
    this._attachedWindowHeight = 0;
    this._debuggerEnabled = true;
    this._profilerEnabled = true;
    this._resourceTrackingEnabled = false;
    this._timelineEnabled = false;
    this._settings = {};
}

WebInspector.InspectorControllerStub.prototype = {
    wrapCallback: function(func)
    {
        return func;
    },

    isWindowVisible: function()
    {
        return this._windowVisible;
    },

    platform: function()
    {
        return "mac-leopard";
    },

    port: function()
    {
        return "unknown";
    },


    closeWindow: function()
    {
        this._windowVisible = false;
    },

    attach: function()
    {
    },

    detach: function()
    {
    },

    storeLastActivePanel: function(panel)
    {
    },

    clearMessages: function()
    {
    },

    searchingForNode: function()
    {
        return this._searchingForNode;
    },

    search: function(sourceRow, query)
    {
    },

    toggleNodeSearch: function()
    {
        this._searchingForNode = !this._searchingForNode;
    },

    setAttachedWindowHeight: function(height)
    {
    },

    moveByUnrestricted: function(x, y)
    {
    },


    addResourceSourceToFrame: function(identifier, element)
    {
    },

    addSourceToFrame: function(mimeType, source, element)
    {
        return false;
    },

    getResourceDocumentNode: function(identifier)
    {
        return undefined;
    },

    highlightDOMNode: function(node)
    {
    },

    hideDOMNodeHighlight: function()
    {
    },

    inspectedWindow: function()
    {
        return window;
    },

    loaded: function()
    {
    },

    localizedStringsURL: function()
    {
        return undefined;
    },

    windowUnloading: function()
    {
        return false;
    },

    hiddenPanels: function()
    {
        return "";
    },

    debuggerEnabled: function()
    {
        return this._debuggerEnabled;
    },

    enableResourceTracking: function()
    {
        this._resourceTrackingEnabled = true;
        WebInspector.resourceTrackingWasEnabled();
    },

    disableResourceTracking: function()
    {
        this._resourceTrackingEnabled = false;
        WebInspector.resourceTrackingWasDisabled();
    },

    resourceTrackingEnabled: function()
    {
        return this._resourceTrackingEnabled;
    },

    enableDebugger: function()
    {
        this._debuggerEnabled = true;
    },

    disableDebugger: function()
    {
        this._debuggerEnabled = false;
    },

    addBreakpoint: function(sourceID, line, condition)
    {
    },

    removeBreakpoint: function(sourceID, line)
    {
    },

    updateBreakpoint: function(sourceID, line, condition)
    {
    },

    pauseInDebugger: function()
    {
    },

    pauseOnExceptions: function()
    {
        return false;
    },

    setPauseOnExceptions: function(value)
    {
    },

    resumeDebugger: function()
    {
    },

    profilerEnabled: function()
    {
        return true;
    },

    enableProfiler: function()
    {
        this._profilerEnabled = true;
    },

    disableProfiler: function()
    {
        this._profilerEnabled = false;
    },

    startProfiling: function()
    {
    },

    stopProfiling: function()
    {
    },

    getProfileHeaders: function(callId)
    {
        WebInspector.didGetProfileHeaders(callId, []);
    },

    getProfile: function(callId, uid)
    {
        if (WebInspector.__fullProfiles && (uid in WebInspector.__fullProfiles))
        {
            WebInspector.didGetProfile(callId, WebInspector.__fullProfiles[uid]);
        }
    },

    takeHeapSnapshot: function()
    {
    },

    databaseTableNames: function(database)
    {
        return [];
    },

    stepIntoStatementInDebugger: function()
    {
    },

    stepOutOfFunctionInDebugger: function()
    {
    },

    stepOverStatementInDebugger: function()
    {
    },

    setSetting: function(setting, value)
    {
        this._settings[setting] = value;
    },

    dispatchOnInjectedScript: function()
    {
    },

    releaseWrapperObjectGroup: function()
    {
    },

    setting: function(setting)
    {
        return this._settings[setting];
    }
}

window.InspectorController = new WebInspector.InspectorControllerStub();

}

/* Object.js */

/*
 * Copyright (C) 2008 Apple Inc. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.Object = function() {
}

WebInspector.Object.prototype = {
    addEventListener: function(eventType, listener, thisObject) {
        if (!("_listeners" in this))
            this._listeners = {};
        if (!(eventType in this._listeners))
            this._listeners[eventType] = [];
        this._listeners[eventType].push({ thisObject: thisObject, listener: listener });
    },

    removeEventListener: function(eventType, listener, thisObject) {
        if (!("_listeners" in this) || !(eventType in this._listeners))
            return;
        var listeners = this._listeners[eventType];
        for (var i = 0; i < listeners.length; ++i) {
            if (listener && listeners[i].listener === listener && listeners[i].thisObject === thisObject)
                listeners.splice(i, 1);
            else if (!listener && thisObject && listeners[i].thisObject === thisObject)
                listeners.splice(i, 1);
        }

        if (!listeners.length)
            delete this._listeners[eventType];
    },

    dispatchEventToListeners: function(eventType) {
        if (!("_listeners" in this) || !(eventType in this._listeners))
            return;

        var stoppedPropagation = false;

        function stopPropagation()
        {
            stoppedPropagation = true;
        }

        function preventDefault()
        {
            this.defaultPrevented = true;
        }

        var event = {target: this, type: eventType, defaultPrevented: false};
        event.stopPropagation = stopPropagation;
        event.preventDefault = preventDefault;

        var listeners = this._listeners[eventType].slice(0);
        for (var i = 0; i < listeners.length; ++i) {
            listeners[i].listener.call(listeners[i].thisObject, event);
            if (stoppedPropagation)
                break;
        }

        return event.defaultPrevented;
    }
}

/* KeyboardShortcut.js */

/*
 * Copyright (C) 2009 Apple Inc. All rights reserved.
 * Copyright (C) 2009 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer. 
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution. 
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission. 
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.KeyboardShortcut = function()
{
};

/**
 * Constants for encoding modifier key set as a bit mask.
 * @see #_makeKeyFromCodeAndModifiers
 */
WebInspector.KeyboardShortcut.Modifiers = {
    None: 0,   // Constant for empty modifiers set.
    Shift: 1,
    Ctrl: 2,
    Alt: 4,
    Meta: 8    // Command key on Mac, Win key on other platforms.
};

WebInspector.KeyboardShortcut.KeyCodes = {
    Esc: 27,
    Space: 32,
    PageUp: 33,      // also NUM_NORTH_EAST
    PageDown: 34,    // also NUM_SOUTH_EAST
    End: 35,         // also NUM_SOUTH_WEST
    Home: 36,        // also NUM_NORTH_WEST
    Left: 37,        // also NUM_WEST
    Up: 38,          // also NUM_NORTH
    Right: 39,       // also NUM_EAST
    Down: 40,        // also NUM_SOUTH
    F1: 112,
    F2: 113,
    F3: 114,
    F4: 115,
    F5: 116,
    F6: 117,
    F7: 118,
    F8: 119,
    F9: 120,
    F10: 121,
    F11: 122,
    F12: 123,
    Semicolon: 186,    // ;
    Comma: 188,        // ,
    Period: 190,       // .
    Slash: 191,        // /
    Apostrophe: 192,   // `
    SingleQuote: 222,  // '
};

/**
 * Creates a number encoding keyCode in the lower 8 bits and modifiers mask in the higher 8 bits.
 * It is usefull for matching pressed keys.
 * @param {number} keyCode Code of the key.
 * @param {number} optModifiers Optional list of modifiers passed as additional paramerters.
 */
WebInspector.KeyboardShortcut.makeKey = function(keyCode, optModifiers)
{
    var modifiers = WebInspector.KeyboardShortcut.Modifiers.None;
    for (var i = 1; i < arguments.length; i++)
        modifiers |= arguments[i];
    return WebInspector.KeyboardShortcut._makeKeyFromCodeAndModifiers(keyCode, modifiers);
};

WebInspector.KeyboardShortcut.makeKeyFromEvent = function(keyboardEvent)
{
    var modifiers = WebInspector.KeyboardShortcut.Modifiers.None;
    if (keyboardEvent.shiftKey)
        modifiers |= WebInspector.KeyboardShortcut.Modifiers.Shift;
    if (keyboardEvent.ctrlKey)
        modifiers |= WebInspector.KeyboardShortcut.Modifiers.Ctrl;
    if (keyboardEvent.altKey)
        modifiers |= WebInspector.KeyboardShortcut.Modifiers.Alt;
    if (keyboardEvent.metaKey)
        modifiers |= WebInspector.KeyboardShortcut.Modifiers.Meta;
    return WebInspector.KeyboardShortcut._makeKeyFromCodeAndModifiers(keyboardEvent.keyCode, modifiers);
};

WebInspector.KeyboardShortcut._makeKeyFromCodeAndModifiers = function(keyCode, modifiers)
{
    return (keyCode & 255) | (modifiers << 8);
};

/* TextPrompt.js */

/*
 * Copyright (C) 2008 Apple Inc.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.TextPrompt = function(element, completions, stopCharacters)
{
    this.element = element;
    this.completions = completions;
    this.completionStopCharacters = stopCharacters;
    this.history = [];
    this.historyOffset = 0;
}

WebInspector.TextPrompt.prototype = {
    get text()
    {
        return this.element.textContent;
    },

    set text(x)
    {
        if (!x) {
            // Append a break element instead of setting textContent to make sure the selection is inside the prompt.
            this.element.removeChildren();
            this.element.appendChild(document.createElement("br"));
        } else
            this.element.textContent = x;

        this.moveCaretToEndOfPrompt();
    },

    handleKeyEvent: function(event)
    {
        switch (event.keyIdentifier) {
            case "Up":
                this._upKeyPressed(event);
                break;
            case "Down":
                this._downKeyPressed(event);
                break;
            case "U+0009": // Tab
                this._tabKeyPressed(event);
                break;
            case "Right":
            case "End":
                if (!this.acceptAutoComplete())
                    this.autoCompleteSoon();
                break;
            default:
                this.clearAutoComplete();
                this.autoCompleteSoon();
                break;
        }
    },

    acceptAutoComplete: function()
    {
        if (!this.autoCompleteElement || !this.autoCompleteElement.parentNode)
            return false;

        var text = this.autoCompleteElement.textContent;
        var textNode = document.createTextNode(text);
        this.autoCompleteElement.parentNode.replaceChild(textNode, this.autoCompleteElement);
        delete this.autoCompleteElement;

        var finalSelectionRange = document.createRange();
        finalSelectionRange.setStart(textNode, text.length);
        finalSelectionRange.setEnd(textNode, text.length);

        var selection = window.getSelection();
        selection.removeAllRanges();
        selection.addRange(finalSelectionRange);

        return true;
    },

    clearAutoComplete: function(includeTimeout)
    {
        if (includeTimeout && "_completeTimeout" in this) {
            clearTimeout(this._completeTimeout);
            delete this._completeTimeout;
        }

        if (!this.autoCompleteElement)
            return;

        if (this.autoCompleteElement.parentNode)
            this.autoCompleteElement.parentNode.removeChild(this.autoCompleteElement);
        delete this.autoCompleteElement;

        if (!this._userEnteredRange || !this._userEnteredText)
            return;

        this._userEnteredRange.deleteContents();

        var userTextNode = document.createTextNode(this._userEnteredText);
        this._userEnteredRange.insertNode(userTextNode);           

        var selectionRange = document.createRange();
        selectionRange.setStart(userTextNode, this._userEnteredText.length);
        selectionRange.setEnd(userTextNode, this._userEnteredText.length);

        var selection = window.getSelection();
        selection.removeAllRanges();
        selection.addRange(selectionRange);

        delete this._userEnteredRange;
        delete this._userEnteredText;
    },

    autoCompleteSoon: function()
    {
        if (!("_completeTimeout" in this))
            this._completeTimeout = setTimeout(this.complete.bind(this, true), 250);
    },

    complete: function(auto)
    {
        this.clearAutoComplete(true);
        var selection = window.getSelection();
        if (!selection.rangeCount)
            return;

        var selectionRange = selection.getRangeAt(0);
        if (!selectionRange.commonAncestorContainer.isDescendant(this.element))
            return;
        if (auto && !this.isCaretAtEndOfPrompt())
            return;
        var wordPrefixRange = selectionRange.startContainer.rangeOfWord(selectionRange.startOffset, this.completionStopCharacters, this.element, "backward");
        this.completions(wordPrefixRange, auto, this._completionsReady.bind(this, selection, auto, wordPrefixRange));
    },

    _completionsReady: function(selection, auto, originalWordPrefixRange, completions)
    {
        if (!completions || !completions.length)
            return;

        var selectionRange = selection.getRangeAt(0);

        var fullWordRange = document.createRange();
        fullWordRange.setStart(originalWordPrefixRange.startContainer, originalWordPrefixRange.startOffset);
        fullWordRange.setEnd(selectionRange.endContainer, selectionRange.endOffset);

        if (originalWordPrefixRange.toString() + selectionRange.toString() != fullWordRange.toString())
            return;

        if (completions.length === 1 || selection.isCollapsed || auto) {
            var completionText = completions[0];
        } else {
            var currentText = fullWordRange.toString();

            var foundIndex = null;
            for (var i = 0; i < completions.length; ++i) {
                if (completions[i] === currentText)
                    foundIndex = i;
            }

            if (foundIndex === null || (foundIndex + 1) >= completions.length)
                var completionText = completions[0];
            else
                var completionText = completions[foundIndex + 1];
        }

        var wordPrefixLength = originalWordPrefixRange.toString().length;

        this._userEnteredRange = fullWordRange;
        this._userEnteredText = fullWordRange.toString();

        fullWordRange.deleteContents();

        var finalSelectionRange = document.createRange();

        if (auto) {
            var prefixText = completionText.substring(0, wordPrefixLength);
            var suffixText = completionText.substring(wordPrefixLength);

            var prefixTextNode = document.createTextNode(prefixText);
            fullWordRange.insertNode(prefixTextNode);           

            this.autoCompleteElement = document.createElement("span");
            this.autoCompleteElement.className = "auto-complete-text";
            this.autoCompleteElement.textContent = suffixText;

            prefixTextNode.parentNode.insertBefore(this.autoCompleteElement, prefixTextNode.nextSibling);

            finalSelectionRange.setStart(prefixTextNode, wordPrefixLength);
            finalSelectionRange.setEnd(prefixTextNode, wordPrefixLength);
        } else {
            var completionTextNode = document.createTextNode(completionText);
            fullWordRange.insertNode(completionTextNode);           

            if (completions.length > 1)
                finalSelectionRange.setStart(completionTextNode, wordPrefixLength);
            else
                finalSelectionRange.setStart(completionTextNode, completionText.length);

            finalSelectionRange.setEnd(completionTextNode, completionText.length);
        }

        selection.removeAllRanges();
        selection.addRange(finalSelectionRange);
    },

    isCaretInsidePrompt: function()
    {
        return this.element.isInsertionCaretInside();
    },

    isCaretAtEndOfPrompt: function()
    {
        var selection = window.getSelection();
        if (!selection.rangeCount || !selection.isCollapsed)
            return false;

        var selectionRange = selection.getRangeAt(0);
        var node = selectionRange.startContainer;
        if (node !== this.element && !node.isDescendant(this.element))
            return false;

        if (node.nodeType === Node.TEXT_NODE && selectionRange.startOffset < node.nodeValue.length)
            return false;

        var foundNextText = false;
        while (node) {
            if (node.nodeType === Node.TEXT_NODE && node.nodeValue.length) {
                if (foundNextText)
                    return false;
                foundNextText = true;
            }

            node = node.traverseNextNode(this.element);
        }

        return true;
    },

    moveCaretToEndOfPrompt: function()
    {
        var selection = window.getSelection();
        var selectionRange = document.createRange();

        var offset = this.element.childNodes.length;
        selectionRange.setStart(this.element, offset);
        selectionRange.setEnd(this.element, offset);

        selection.removeAllRanges();
        selection.addRange(selectionRange);
    },

    _tabKeyPressed: function(event)
    {
        event.preventDefault();
        event.stopPropagation();

        this.complete();
    },

    _upKeyPressed: function(event)
    {
        event.preventDefault();
        event.stopPropagation();

        if (this.historyOffset == this.history.length)
            return;

        this.clearAutoComplete(true);

        if (this.historyOffset == 0)
            this.tempSavedCommand = this.text;

        ++this.historyOffset;
        this.text = this.history[this.history.length - this.historyOffset];
    },

    _downKeyPressed: function(event)
    {
        event.preventDefault();
        event.stopPropagation();

        if (this.historyOffset == 0)
            return;

        this.clearAutoComplete(true);

        --this.historyOffset;

        if (this.historyOffset == 0) {
            this.text = this.tempSavedCommand;
            delete this.tempSavedCommand;
            return;
        }

        this.text = this.history[this.history.length - this.historyOffset];
    }
}

/* Popup.js */

/*
 * Copyright (C) 2009 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/**
 * This class provides a popup that can be shown relative to an anchor element
 * or at an arbitrary absolute position.
 * Points are Objects: {x: xValue, y: yValue}.
 * Rectangles are Objects: {x: xValue, y: yValue, width: widthValue, height: heightValue}.
 *
 * element is an optional unparented visible element (style.display != "none" AND style.visibility != "hidden").
 * If the element is absent/undefined, it must have been set with the element(x) setter before the show() method invocation.
 */
WebInspector.Popup = function(element)
{
    if (element)
        this.element = element;
    this._keyHandler = this._keyEventHandler.bind(this);
    this._mouseDownHandler = this._mouseDownEventHandler.bind(this);
    this._visible = false;
    this._autoHide = true;
}

WebInspector.Popup.prototype = {
    show: function()
    {
        if (this.visible)
            return;
        var ownerDocument = this._contentElement.ownerDocument;
        if (!ownerDocument)
            return;

        this._glasspaneElement = ownerDocument.createElement("div");
        this._glasspaneElement.className = "popup-glasspane";
        ownerDocument.body.appendChild(this._glasspaneElement);

        this._contentElement.positionAt(0, 0);
        this._contentElement.removeStyleClass("hidden");
        ownerDocument.body.appendChild(this._contentElement);

        this.positionElement();
        this._visible = true;
        ownerDocument.addEventListener("keydown", this._keyHandler, false);
        ownerDocument.addEventListener("mousedown", this._mouseDownHandler, false);
    },

    hide: function()
    {
        if (this.visible) {
            this._visible = false;
            this._contentElement.ownerDocument.removeEventListener("keydown", this._keyHandler, false);
            this._contentElement.ownerDocument.removeEventListener("mousedown", this._mouseDownHandler, false);
            this._glasspaneElement.parentElement.removeChild(this._glasspaneElement);
            this._contentElement.parentElement.removeChild(this._contentElement);
        }
    },

    get visible()
    {
        return this._visible;
    },

    set element(x)
    {
        this._checkNotVisible();
        this._contentElement = x;
        this._contentElement.addStyleClass("hidden");
    },

    get element()
    {
        return this._contentElement;
    },

    positionElement: function()
    {
        var element = this._contentElement;
        var anchorElement = this._anchorElement;

        var targetDocument = element.ownerDocument;
        var targetDocumentBody = targetDocument.body;
        var targetDocumentElement = targetDocument.documentElement;
        var clippingBox = {x: 0, y: 0, width: targetDocumentElement.clientWidth, height: targetDocumentElement.clientHeight};
        var parentElement = element.offsetParent || element.parentElement;

        var anchorPosition = {x: anchorElement.totalOffsetLeft, y: anchorElement.totalOffsetTop};

        // FIXME(apavlov@chromium.org): Translate anchorPosition to the element.ownerDocument frame when https://bugs.webkit.org/show_bug.cgi?id=28913 is fixed.
        var anchorBox = {x: anchorPosition.x, y: anchorPosition.y, width: anchorElement.offsetWidth, height: anchorElement.offsetHeight};
        var elementBox = {x: element.totalOffsetLeft, y: element.totalOffsetTop, width: element.offsetWidth, height: element.offsetHeight};
        var newElementPosition = {x: 0, y: 0};

        if (anchorBox.y - elementBox.height >= clippingBox.y)
            newElementPosition.y = anchorBox.y - elementBox.height;
        else
            newElementPosition.y = Math.min(anchorBox.y + anchorBox.height, Math.max(clippingBox.y, clippingBox.y + clippingBox.height - elementBox.height));

        if (anchorBox.x + elementBox.height <= clippingBox.x + clippingBox.height)
            newElementPosition.x = anchorBox.x;
        else
            newElementPosition.x = Math.max(clippingBox.x, clippingBox.x + clippingBox.height - elementBox.height);
        element.positionAt(newElementPosition.x, newElementPosition.y);
    },

    set anchor(x)
    {
        this._checkNotVisible();
        this._anchorElement = x;
    },

    get anchor()
    {
        return this._anchorElement;
    },

    set autoHide(x)
    {
        this._autoHide = x;
    },

    _checkNotVisible: function()
    {
        if (this.visible)
            throw new Error("The popup must not be visible.");
    },

    _keyEventHandler: function(event)
    {
        // Escape hides the popup.
        if (event.keyIdentifier == "U+001B") {
            this.hide();
            event.preventDefault();
            event.handled = true;
        }
    },

    _mouseDownEventHandler: function(event)
    {
        if (this._autoHide && event.originalTarget === this._glasspaneElement)
            this.hide();
    }
}

/* Placard.js */

/*
 * Copyright (C) 2008 Apple Inc. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.Placard = function(title, subtitle)
{
    this.element = document.createElement("div");
    this.element.className = "placard";
    this.element.placard = this;

    this.titleElement = document.createElement("div");
    this.titleElement.className = "title";

    this.subtitleElement = document.createElement("div");
    this.subtitleElement.className = "subtitle";

    this.element.appendChild(this.subtitleElement);
    this.element.appendChild(this.titleElement);

    this.title = title;
    this.subtitle = subtitle;
    this.selected = false;
}

WebInspector.Placard.prototype = {
    get title()
    {
        return this._title;
    },

    set title(x)
    {
        if (this._title === x)
            return;
        this._title = x;
        this.titleElement.textContent = x;
    },

    get subtitle()
    {
        return this._subtitle;
    },

    set subtitle(x)
    {
        if (this._subtitle === x)
            return;
        this._subtitle = x;
        this.subtitleElement.innerHTML = x;
    },

    get selected()
    {
        return this._selected;
    },

    set selected(x)
    {
        if (x)
            this.select();
        else
            this.deselect();
    },

    select: function()
    {
        if (this._selected)
            return;
        this._selected = true;
        this.element.addStyleClass("selected");
    },

    deselect: function()
    {
        if (!this._selected)
            return;
        this._selected = false;
        this.element.removeStyleClass("selected");
    },

    toggleSelected: function()
    {
        this.selected = !this.selected;
    }
}

/* View.js */

/*
 * Copyright (C) 2008 Apple Inc. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.View = function(element)
{
    this.element = element || document.createElement("div");
    this._visible = false;
}

WebInspector.View.prototype = {
    get visible()
    {
        return this._visible;
    },

    set visible(x)
    {
        if (this._visible === x)
            return;

        if (x)
            this.show();
        else
            this.hide();
    },

    show: function(parentElement)
    {
        this._visible = true;
        if (parentElement && parentElement !== this.element.parentNode) {
            this.detach();
            parentElement.appendChild(this.element);
        }
        if (!this.element.parentNode && this.attach)
            this.attach();
        this.element.addStyleClass("visible");
    },

    hide: function()
    {
        this.element.removeStyleClass("visible");
        this._visible = false;
    },

    detach: function()
    {
        if (this.element.parentNode)
            this.element.parentNode.removeChild(this.element);
    }
}

WebInspector.View.prototype.__proto__ = WebInspector.Object.prototype;

/* Callback.js */

/*
 * Copyright (C) 2009 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.Callback = function()
{
    this._lastCallbackId = 1;
    this._callbacks = {};
}

WebInspector.Callback.prototype = {
    wrap: function(callback)
    {
        var callbackId = this._lastCallbackId++;
        this._callbacks[callbackId] = callback || function() {};
        return callbackId;
    },

    processCallback: function(callbackId, opt_vararg)
    {
        var args = Array.prototype.slice.call(arguments, 1);
        var callback = this._callbacks[callbackId];
        callback.apply(null, args);
        delete this._callbacks[callbackId];
    }
}

WebInspector.Callback._INSTANCE = new WebInspector.Callback();
WebInspector.Callback.wrap = WebInspector.Callback._INSTANCE.wrap.bind(WebInspector.Callback._INSTANCE);
WebInspector.Callback.processCallback = WebInspector.Callback._INSTANCE.processCallback.bind(WebInspector.Callback._INSTANCE);

/* Drawer.js */

/*
 * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
 * Copyright (C) 2009 Joseph Pecoraro
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer. 
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution. 
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission. 
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.Drawer = function()
{
    WebInspector.View.call(this, document.getElementById("drawer"));

    this.mainStatusBar = document.getElementById("main-status-bar");
    this.mainStatusBar.addEventListener("mousedown", this._startStatusBarDragging.bind(this), true);
    this.viewStatusBar = document.getElementById("other-drawer-status-bar-items");
}

WebInspector.Drawer.prototype = {
    get visibleView()
    {
        return this._visibleView;
    },

    set visibleView(x)
    {
        if (this._visibleView === x) {
            this.visible = !this.visible;
            return;
        }

        var firstTime = !this._visibleView;
        if (this._visibleView)
            this._visibleView.hide();

        this._visibleView = x;

        if (x && !firstTime) {
            this._safelyRemoveChildren();
            this.viewStatusBar.removeChildren(); // optimize this? call old.detach()
            x.attach(this.element, this.viewStatusBar);
            x.show();
            this.visible = true;
        }
    },

    showView: function(view)
    {
        if (!this.visible || this.visibleView !== view)
            this.visibleView = view;
    },

    show: function()
    {
        if (this._animating || this.visible)
            return;

        if (this.visibleView)
            this.visibleView.show();

        WebInspector.View.prototype.show.call(this);

        this._animating = true;

        document.body.addStyleClass("drawer-visible");

        var anchoredItems = document.getElementById("anchored-status-bar-items");

        var animations = [
            {element: document.getElementById("main"), end: {bottom: this.element.offsetHeight}},
            {element: document.getElementById("main-status-bar"), start: {"padding-left": anchoredItems.offsetWidth - 1}, end: {"padding-left": 0}},
            {element: document.getElementById("other-drawer-status-bar-items"), start: {opacity: 0}, end: {opacity: 1}}
        ];

        var consoleStatusBar = document.getElementById("drawer-status-bar");
        consoleStatusBar.insertBefore(anchoredItems, consoleStatusBar.firstChild);

        function animationFinished()
        {
            if ("updateStatusBarItems" in WebInspector.currentPanel)
                WebInspector.currentPanel.updateStatusBarItems();
            if (this.visibleView.afterShow)
                this.visibleView.afterShow();
            delete this._animating;
        }

        WebInspector.animateStyle(animations, window.event && window.event.shiftKey ? 2000 : 250, animationFinished.bind(this));
    },

    hide: function()
    {
        if (this._animating || !this.visible)
            return;

        WebInspector.View.prototype.hide.call(this);
        
        if (this.visibleView)
            this.visibleView.hide();

        this._animating = true;

        if (this.element === WebInspector.currentFocusElement || this.element.isAncestor(WebInspector.currentFocusElement))
            WebInspector.currentFocusElement = WebInspector.previousFocusElement;

        var anchoredItems = document.getElementById("anchored-status-bar-items");

        // Temporally set properties and classes to mimic the post-animation values so panels
        // like Elements in their updateStatusBarItems call will size things to fit the final location.
        document.getElementById("main-status-bar").style.setProperty("padding-left", (anchoredItems.offsetWidth - 1) + "px");
        document.body.removeStyleClass("drawer-visible");
        if ("updateStatusBarItems" in WebInspector.currentPanel)
            WebInspector.currentPanel.updateStatusBarItems();
        document.body.addStyleClass("drawer-visible");

        var animations = [
            {element: document.getElementById("main"), end: {bottom: 0}},
            {element: document.getElementById("main-status-bar"), start: {"padding-left": 0}, end: {"padding-left": anchoredItems.offsetWidth - 1}},
            {element: document.getElementById("other-drawer-status-bar-items"), start: {opacity: 1}, end: {opacity: 0}}
        ];

        function animationFinished()
        {
            var mainStatusBar = document.getElementById("main-status-bar");
            mainStatusBar.insertBefore(anchoredItems, mainStatusBar.firstChild);
            mainStatusBar.style.removeProperty("padding-left");
            document.body.removeStyleClass("drawer-visible");
            delete this._animating;
        }

        WebInspector.animateStyle(animations, window.event && window.event.shiftKey ? 2000 : 250, animationFinished.bind(this));
    },

    _safelyRemoveChildren: function()
    {
        var child = this.element.firstChild;
        while (child) {
            if (child.id !== "drawer-status-bar") {
                var moveTo = child.nextSibling;
                this.element.removeChild(child);
                child = moveTo;
            } else
                child = child.nextSibling;
        }
    },

    _startStatusBarDragging: function(event)
    {
        if (!this.visible || event.target !== document.getElementById("main-status-bar"))
            return;

        WebInspector.elementDragStart(document.getElementById("main-status-bar"), this._statusBarDragging.bind(this), this._endStatusBarDragging.bind(this), event, "row-resize");

        this._statusBarDragOffset = event.pageY - this.element.totalOffsetTop;

        event.stopPropagation();
    },

    _statusBarDragging: function(event)
    {
        var mainElement = document.getElementById("main");

        var height = window.innerHeight - event.pageY + this._statusBarDragOffset;
        height = Number.constrain(height, Preferences.minConsoleHeight, window.innerHeight - mainElement.totalOffsetTop - Preferences.minConsoleHeight);

        mainElement.style.bottom = height + "px";
        this.element.style.height = height + "px";

        event.preventDefault();
        event.stopPropagation();
    },

    _endStatusBarDragging: function(event)
    {
        WebInspector.elementDragEnd(event);

        delete this._statusBarDragOffset;

        event.stopPropagation();
    }
}

WebInspector.Drawer.prototype.__proto__ = WebInspector.View.prototype;

/* ChangesView.js */

/*
 * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
 * Copyright (C) 2009 Joseph Pecoraro
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer. 
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution. 
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission. 
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.ChangesView = function(drawer)
{
    WebInspector.View.call(this);
    this.element.innerHTML = "<div style=\"bottom:25%;color:rgb(192,192,192);font-size:12px;height:65px;left:0px;margin:auto;position:absolute;right:0px;text-align:center;top:0px;\"><h1>Not Implemented Yet</h1></div>";

    this.drawer = drawer;

    this.clearButton = document.createElement("button");
    this.clearButton.id = "clear-changes-status-bar-item";
    this.clearButton.title = WebInspector.UIString("Clear changes log.");
    this.clearButton.className = "status-bar-item";
    this.clearButton.addEventListener("click", this._clearButtonClicked.bind(this), false);

    this.toggleChangesButton = document.getElementById("changes-status-bar-item");
    this.toggleChangesButton.title = WebInspector.UIString("Show changes view.");
    this.toggleChangesButton.addEventListener("click", this._toggleChangesButtonClicked.bind(this), false);
    var anchoredStatusBar = document.getElementById("anchored-status-bar-items");
    anchoredStatusBar.appendChild(this.toggleChangesButton);
}

WebInspector.ChangesView.prototype = {
    _clearButtonClicked: function()
    {
        // Not Implemented Yet
    },

    _toggleChangesButtonClicked: function()
    {
        this.drawer.visibleView = this;
    },

    attach: function(mainElement, statusBarElement)
    {
        mainElement.appendChild(this.element);
        statusBarElement.appendChild(this.clearButton);
    },

    show: function()
    {
        this.toggleChangesButton.addStyleClass("toggled-on");
        this.toggleChangesButton.title = WebInspector.UIString("Hide changes view.");
    },

    hide: function()
    {
        this.toggleChangesButton.removeStyleClass("toggled-on");
        this.toggleChangesButton.title = WebInspector.UIString("Show changes view.");
    }
}

WebInspector.ChangesView.prototype.__proto__ = WebInspector.View.prototype;

/* ConsoleView.js */

/*
 * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
 * Copyright (C) 2009 Joseph Pecoraro
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer. 
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution. 
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission. 
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

const ExpressionStopCharacters = " =:[({;,!+-*/&|^<>";

WebInspector.ConsoleView = function(drawer)
{
    WebInspector.View.call(this, document.getElementById("console-view"));

    this.messages = [];
    this.drawer = drawer;

    this.clearButton = document.getElementById("clear-console-status-bar-item");
    this.clearButton.title = WebInspector.UIString("Clear console log.");
    this.clearButton.addEventListener("click", this._clearButtonClicked.bind(this), false);

    this.messagesElement = document.getElementById("console-messages");
    this.messagesElement.addEventListener("selectstart", this._messagesSelectStart.bind(this), false);
    this.messagesElement.addEventListener("click", this._messagesClicked.bind(this), true);

    this.promptElement = document.getElementById("console-prompt");
    this.promptElement.handleKeyEvent = this._promptKeyDown.bind(this);
    this.prompt = new WebInspector.TextPrompt(this.promptElement, this.completions.bind(this), ExpressionStopCharacters + ".");

    this.topGroup = new WebInspector.ConsoleGroup(null, 0);
    this.messagesElement.insertBefore(this.topGroup.element, this.promptElement);
    this.groupLevel = 0;
    this.currentGroup = this.topGroup;

    this.toggleConsoleButton = document.getElementById("console-status-bar-item");
    this.toggleConsoleButton.title = WebInspector.UIString("Show console.");
    this.toggleConsoleButton.addEventListener("click", this._toggleConsoleButtonClicked.bind(this), false);

    var anchoredStatusBar = document.getElementById("anchored-status-bar-items");
    anchoredStatusBar.appendChild(this.toggleConsoleButton);
    
    // Will hold the list of filter elements
    this.filterBarElement = document.getElementById("console-filter");
    
    function createDividerElement() {
        var dividerElement = document.createElement("div");
        
        dividerElement.addStyleClass("divider");
        
        this.filterBarElement.appendChild(dividerElement);
    }
    
    function createFilterElement(category) {
        var categoryElement = document.createElement("li");
        categoryElement.category = category;
     
        categoryElement.addStyleClass(categoryElement.category);
            
        var label = category.toString();
        categoryElement.appendChild(document.createTextNode(label));
     
        categoryElement.addEventListener("click", this._updateFilter.bind(this), false);
     
        this.filterBarElement.appendChild(categoryElement);
        return categoryElement;
    }
    
    this.allElement = createFilterElement.call(this, "All");
    
    createDividerElement.call(this);
    
    this.errorElement = createFilterElement.call(this, "Errors");
    this.warningElement = createFilterElement.call(this, "Warnings");
    this.logElement = createFilterElement.call(this, "Logs");

    this.filter(this.allElement, false);
}

WebInspector.ConsoleView.prototype = {
    
    _updateFilter: function(e)
    {
        var isMac = InspectorController.platform().indexOf("mac-") === 0;
        var selectMultiple = false;
        if (isMac && e.metaKey && !e.ctrlKey && !e.altKey && !e.shiftKey)
            selectMultiple = true;
        if (!isMac && e.ctrlKey && !e.metaKey && !e.altKey && !e.shiftKey)
            selectMultiple = true;

        this.filter(e.target, selectMultiple);
    },
    
    filter: function(target, selectMultiple)
    {
        function unselectAll()
        {
            this.allElement.removeStyleClass("selected");
            this.errorElement.removeStyleClass("selected");
            this.warningElement.removeStyleClass("selected");
            this.logElement.removeStyleClass("selected");
            
            this.messagesElement.removeStyleClass("filter-all");
            this.messagesElement.removeStyleClass("filter-errors");
            this.messagesElement.removeStyleClass("filter-warnings");
            this.messagesElement.removeStyleClass("filter-logs");
        }
        
        var targetFilterClass = "filter-" + target.category.toLowerCase();

        if (target.category == "All") {
            if (target.hasStyleClass("selected")) {
                // We can't unselect all, so we break early here
                return;
            }

            unselectAll.call(this);
        } else {
            // Something other than all is being selected, so we want to unselect all
            if (this.allElement.hasStyleClass("selected")) {
                this.allElement.removeStyleClass("selected");
                this.messagesElement.removeStyleClass("filter-all");
            }
        }
        
        if (!selectMultiple) {
            // If multiple selection is off, we want to unselect everything else
            // and just select ourselves.
            unselectAll.call(this);
            
            target.addStyleClass("selected");
            this.messagesElement.addStyleClass(targetFilterClass);
            
            return;
        }
        
        if (target.hasStyleClass("selected")) {
            // If selectMultiple is turned on, and we were selected, we just
            // want to unselect ourselves.
            target.removeStyleClass("selected");
            this.messagesElement.removeStyleClass(targetFilterClass);
        } else {
            // If selectMultiple is turned on, and we weren't selected, we just
            // want to select ourselves.
            target.addStyleClass("selected");
            this.messagesElement.addStyleClass(targetFilterClass);
        }
    },
    
    _toggleConsoleButtonClicked: function()
    {
        this.drawer.visibleView = this;
    },

    attach: function(mainElement, statusBarElement)
    {
        mainElement.appendChild(this.element);
        statusBarElement.appendChild(this.clearButton);
        statusBarElement.appendChild(this.filterBarElement);
    },

    show: function()
    {
        this.toggleConsoleButton.addStyleClass("toggled-on");
        this.toggleConsoleButton.title = WebInspector.UIString("Hide console.");
        if (!this.prompt.isCaretInsidePrompt())
            this.prompt.moveCaretToEndOfPrompt();
    },

    afterShow: function()
    {
        WebInspector.currentFocusElement = this.promptElement;  
    },

    hide: function()
    {
        this.toggleConsoleButton.removeStyleClass("toggled-on");
        this.toggleConsoleButton.title = WebInspector.UIString("Show console.");
    },

    addMessage: function(msg)
    {
        if (msg instanceof WebInspector.ConsoleMessage && !(msg instanceof WebInspector.ConsoleCommandResult)) {
            this._incrementErrorWarningCount(msg);

            // Add message to the resource panel
            if (msg.url in WebInspector.resourceURLMap) {
                msg.resource = WebInspector.resourceURLMap[msg.url];
                if (WebInspector.panels.resources)
                    WebInspector.panels.resources.addMessageToResource(msg.resource, msg);
            }

            this.commandSincePreviousMessage = false;
            this.previousMessage = msg;
        } else if (msg instanceof WebInspector.ConsoleCommand) {
            if (this.previousMessage) {
                this.commandSincePreviousMessage = true;
            }
        }

        this.messages.push(msg);

        if (msg.type === WebInspector.ConsoleMessage.MessageType.EndGroup) {
            if (this.groupLevel < 1)
                return;

            this.groupLevel--;

            this.currentGroup = this.currentGroup.parentGroup;
        } else {
            if (msg.type === WebInspector.ConsoleMessage.MessageType.StartGroup) {
                this.groupLevel++;

                var group = new WebInspector.ConsoleGroup(this.currentGroup, this.groupLevel);
                this.currentGroup.messagesElement.appendChild(group.element);
                this.currentGroup = group;
            }

            this.currentGroup.addMessage(msg);
        }

        this.promptElement.scrollIntoView(false);
    },

    updateMessageRepeatCount: function(count) {
        var msg = this.previousMessage;
        var prevRepeatCount = msg.totalRepeatCount;
        
        if (!this.commandSincePreviousMessage) {
            msg.repeatDelta = count - prevRepeatCount;
            msg.repeatCount = msg.repeatCount + msg.repeatDelta;
            msg.totalRepeatCount = count;
            msg._updateRepeatCount();
            this._incrementErrorWarningCount(msg);
        } else {
            msgCopy = new WebInspector.ConsoleMessage(msg.source, msg.type, msg.level, msg.line, msg.url, msg.groupLevel, count - prevRepeatCount);
            msgCopy.totalRepeatCount = count;
            msgCopy.setMessageBody(msg.args);
            this.addMessage(msgCopy);
        }
    },

    _incrementErrorWarningCount: function(msg) {
        switch (msg.level) {
            case WebInspector.ConsoleMessage.MessageLevel.Warning:
                WebInspector.warnings += msg.repeatDelta;
                break;
            case WebInspector.ConsoleMessage.MessageLevel.Error:
                WebInspector.errors += msg.repeatDelta;
                break;
        }
    },

    clearMessages: function(clearInspectorController)
    {
        if (clearInspectorController)
            InspectorController.clearMessages(false);
        if (WebInspector.panels.resources)
            WebInspector.panels.resources.clearMessages();

        this.messages = [];

        this.groupLevel = 0;
        this.currentGroup = this.topGroup;
        this.topGroup.messagesElement.removeChildren();

        WebInspector.errors = 0;
        WebInspector.warnings = 0;

        delete this.commandSincePreviousMessage;
        delete this.previousMessage;
    },

    completions: function(wordRange, bestMatchOnly, completionsReadyCallback)
    {
        // Pass less stop characters to rangeOfWord so the range will be a more complete expression.
        var expressionRange = wordRange.startContainer.rangeOfWord(wordRange.startOffset, ExpressionStopCharacters, this.promptElement, "backward");
        var expressionString = expressionRange.toString();
        var lastIndex = expressionString.length - 1;

        var dotNotation = (expressionString[lastIndex] === ".");
        var bracketNotation = (expressionString[lastIndex] === "[");

        if (dotNotation || bracketNotation)
            expressionString = expressionString.substr(0, lastIndex);

        var prefix = wordRange.toString();
        if (!expressionString && !prefix)
            return;

        var reportCompletions = this._reportCompletions.bind(this, bestMatchOnly, completionsReadyCallback, dotNotation, bracketNotation, prefix);
        // Collect comma separated object properties for the completion.

        var includeInspectorCommandLineAPI = (!dotNotation && !bracketNotation);
        if (WebInspector.panels.scripts && WebInspector.panels.scripts.paused)
            var callFrameId = WebInspector.panels.scripts.selectedCallFrameId();
        InjectedScriptAccess.getCompletions(expressionString, includeInspectorCommandLineAPI, callFrameId, reportCompletions);
    },

    _reportCompletions: function(bestMatchOnly, completionsReadyCallback, dotNotation, bracketNotation, prefix, result, isException) {
        if (isException)
            return;

        if (bracketNotation) {
            if (prefix.length && prefix[0] === "'")
                var quoteUsed = "'";
            else
                var quoteUsed = "\"";
        }

        var results = [];
        var properties = Object.sortedProperties(result);

        for (var i = 0; i < properties.length; ++i) {
            var property = properties[i];

            if (dotNotation && !/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(property))
                continue;

            if (bracketNotation) {
                if (!/^[0-9]+$/.test(property))
                    property = quoteUsed + property.escapeCharacters(quoteUsed + "\\") + quoteUsed;
                property += "]";
            }

            if (property.length < prefix.length)
                continue;
            if (property.indexOf(prefix) !== 0)
                continue;

            results.push(property);
            if (bestMatchOnly)
                break;
        }
        completionsReadyCallback(results);
    },

    _clearButtonClicked: function()
    {
        this.clearMessages(true);
    },

    _messagesSelectStart: function(event)
    {
        if (this._selectionTimeout)
            clearTimeout(this._selectionTimeout);

        this.prompt.clearAutoComplete();

        function moveBackIfOutside()
        {
            delete this._selectionTimeout;
            if (!this.prompt.isCaretInsidePrompt() && window.getSelection().isCollapsed)
                this.prompt.moveCaretToEndOfPrompt();
            this.prompt.autoCompleteSoon();
        }

        this._selectionTimeout = setTimeout(moveBackIfOutside.bind(this), 100);
    },

    _messagesClicked: function(event)
    {
        var link = event.target.enclosingNodeOrSelfWithNodeName("a");
        if (!link || !link.representedNode)
            return;

        WebInspector.updateFocusedNode(link.representedNode.id);
        event.stopPropagation();
        event.preventDefault();
    },

    _promptKeyDown: function(event)
    {
        if (isEnterKey(event)) {
            this._enterKeyPressed(event);
            return;
        }
        if (isFnKey(event)) {
            if (WebInspector.currentPanel && WebInspector.currentPanel.handleKeyEvent) {
                WebInspector.currentPanel.handleKeyEvent(event);
                return;
            }
        }

        this.prompt.handleKeyEvent(event);
    },

    evalInInspectedWindow: function(expression, objectGroup, callback)
    {
        if (WebInspector.panels.scripts && WebInspector.panels.scripts.paused) {
            WebInspector.panels.scripts.evaluateInSelectedCallFrame(expression, false, objectGroup, callback);
            return;
        }
        this.doEvalInWindow(expression, objectGroup, callback);
    },

    doEvalInWindow: function(expression, objectGroup, callback)
    {
        if (!expression) {
            // There is no expression, so the completion should happen against global properties.
            expression = "this";
        }

        function evalCallback(result)
        {
            callback(result.value, result.isException);
        };
        InjectedScriptAccess.evaluate(expression, objectGroup, evalCallback);
    },

    _enterKeyPressed: function(event)
    {
        if (event.altKey)
            return;

        event.preventDefault();
        event.stopPropagation();

        this.prompt.clearAutoComplete(true);

        var str = this.prompt.text;
        if (!str.length)
            return;

        var commandMessage = new WebInspector.ConsoleCommand(str);
        this.addMessage(commandMessage);

        var self = this;
        function printResult(result, exception)
        {
            self.prompt.history.push(str);
            self.prompt.historyOffset = 0;
            self.prompt.text = "";
            self.addMessage(new WebInspector.ConsoleCommandResult(result, exception, commandMessage));
        }
        this.evalInInspectedWindow(str, "console", printResult);
    },

    _format: function(output, forceObjectFormat)
    {
        var isProxy = (output != null && typeof output === "object");

        if (forceObjectFormat)
            var type = "object";
        else
            var type = Object.proxyType(output);

        if (isProxy && type !== "object" && type !== "function" && type !== "array" && type !== "node") {
            // Unwrap primitive value, skip decoration.
            output = output.description;
            type = "undecorated"
        }

        // We don't perform any special formatting on these types, so we just
        // pass them through the simple _formatvalue function.
        var undecoratedTypes = {
            "undefined": 1,
            "null": 1,
            "boolean": 1,
            "number": 1,
            "undecorated": 1
        };

        var formatter;
        if (forceObjectFormat)
            formatter = "_formatobject";
        else if (type in undecoratedTypes)
            formatter = "_formatvalue";
        else {
            formatter = "_format" + type;
            if (!(formatter in this)) {
                formatter = "_formatobject";
                type = "object";
            }
        }

        var span = document.createElement("span");
        span.addStyleClass("console-formatted-" + type);
        this[formatter](output, span);
        return span;
    },

    _formatvalue: function(val, elem)
    {
        elem.appendChild(document.createTextNode(val));
    },

    _formatfunction: function(func, elem)
    {
        elem.appendChild(document.createTextNode(func.description));
    },

    _formatdate: function(date, elem)
    {
        elem.appendChild(document.createTextNode(date));
    },

    _formatstring: function(str, elem)
    {
        elem.appendChild(document.createTextNode("\"" + str + "\""));
    },

    _formatregexp: function(re, elem)
    {
        var formatted = String(re.description).replace(/([\\\/])/g, "\\$1").replace(/\\(\/[gim]*)$/, "$1").substring(1);
        elem.appendChild(document.createTextNode(formatted));
    },

    _formatarray: function(arr, elem)
    {
        InjectedScriptAccess.getProperties(arr, false, this._printArray.bind(this, elem));
    },

    _printArray: function(elem, properties)
    {
        if (!properties)
            return;
        var elements = [];
        for (var i = 0; i < properties.length; ++i) {
            var name = properties[i].name;
            if (name == parseInt(name))
                elements[name] = this._format(properties[i].value);
        }

        elem.appendChild(document.createTextNode("["));
        for (var i = 0; i < elements.length; ++i) {
            var element = elements[i];
            if (element)
                elem.appendChild(element);
            else
                elem.appendChild(document.createTextNode("undefined"))
            if (i < elements.length - 1)
                elem.appendChild(document.createTextNode(", "));
        }
        elem.appendChild(document.createTextNode("]"));
    },

    _formatnode: function(object, elem)
    {
        function printNode(nodeId)
        {
            if (!nodeId)
                return;
            var treeOutline = new WebInspector.ElementsTreeOutline();
            treeOutline.showInElementsPanelEnabled = true;
            treeOutline.rootDOMNode = WebInspector.domAgent.nodeForId(nodeId);
            treeOutline.element.addStyleClass("outline-disclosure");
            if (!treeOutline.children[0].hasChildren)
                treeOutline.element.addStyleClass("single-node");
            elem.appendChild(treeOutline.element);
        }
        InjectedScriptAccess.pushNodeToFrontend(object, printNode);
    },

    _formatobject: function(obj, elem)
    {
        elem.appendChild(new WebInspector.ObjectPropertiesSection(obj, obj.description, null, true).element);
    },

    _formaterror: function(obj, elem)
    {
        var messageElement = document.createElement("span");
        messageElement.className = "error-message";
        messageElement.textContent = obj.name + ": " + obj.message;
        elem.appendChild(messageElement);

        if (obj.sourceURL) {
            var urlElement = document.createElement("a");
            urlElement.className = "webkit-html-resource-link";
            urlElement.href = obj.sourceURL;
            urlElement.lineNumber = obj.line;
            urlElement.preferredPanel = "scripts";

            if (obj.line > 0)
                urlElement.textContent = WebInspector.displayNameForURL(obj.sourceURL) + ":" + obj.line;
            else
                urlElement.textContent = WebInspector.displayNameForURL(obj.sourceURL);

            elem.appendChild(document.createTextNode(" ("));
            elem.appendChild(urlElement);
            elem.appendChild(document.createTextNode(")"));
        }
    }
}

WebInspector.ConsoleView.prototype.__proto__ = WebInspector.View.prototype;

WebInspector.ConsoleMessage = function(source, type, level, line, url, groupLevel, repeatCount)
{
    this.source = source;
    this.type = type;
    this.level = level;
    this.line = line;
    this.url = url;
    this.groupLevel = groupLevel;
    this.repeatCount = repeatCount;
    this.repeatDelta = repeatCount;
    this.totalRepeatCount = repeatCount;
    if (arguments.length > 7)
        this.setMessageBody(Array.prototype.slice.call(arguments, 7));
}

WebInspector.ConsoleMessage.prototype = {
    setMessageBody: function(args)
    {
        this.args = args;
        switch (this.type) {
            case WebInspector.ConsoleMessage.MessageType.Trace:
                var span = document.createElement("span");
                span.addStyleClass("console-formatted-trace");
                var stack = Array.prototype.slice.call(args);
                var funcNames = stack.map(function(f) {
                    return f || WebInspector.UIString("(anonymous function)");
                });
                span.appendChild(document.createTextNode(funcNames.join("\n")));
                this.formattedMessage = span;
                break;
            case WebInspector.ConsoleMessage.MessageType.Object:
                this.formattedMessage = this._format(["%O", args[0]]);
                break;
            default:
                this.formattedMessage = this._format(args);
                break;
        }

        // This is used for inline message bubbles in SourceFrames, or other plain-text representations.
        this.message = this.formattedMessage.textContent;
    },

    isErrorOrWarning: function()
    {
        return (this.level === WebInspector.ConsoleMessage.MessageLevel.Warning || this.level === WebInspector.ConsoleMessage.MessageLevel.Error);
    },

    _format: function(parameters)
    {
        var formattedResult = document.createElement("span");

        if (!parameters.length)
            return formattedResult;

        // Formatting code below assumes that parameters are all wrappers whereas frontend console
        // API allows passing arbitrary values as messages (strings, numberts, etc.). Wrap them here.
        for (var i = 0; i < parameters.length; ++i) {
            if (typeof parameters[i] !== "object" && typeof parameters[i] !== "function")
                parameters[i] = WebInspector.ObjectProxy.wrapPrimitiveValue(parameters[i]);
        }

        function formatForConsole(obj)
        {
            return WebInspector.console._format(obj);
        }

        function formatAsObjectForConsole(obj)
        {
            return WebInspector.console._format(obj, true);
        }

        if (Object.proxyType(parameters[0]) === "string") {
            var formatters = {}
            for (var i in String.standardFormatters)
                formatters[i] = String.standardFormatters[i];

            // Firebug uses %o for formatting objects.
            formatters.o = formatForConsole;
            // Firebug allows both %i and %d for formatting integers.
            formatters.i = formatters.d;
            // Support %O to force object formating, instead of the type-based %o formatting.
            formatters.O = formatAsObjectForConsole;

            function append(a, b)
            {
                if (!(b instanceof Node))
                    a.appendChild(WebInspector.linkifyStringAsFragment(b.toString()));
                else
                    a.appendChild(b);
                return a;
            }

            var result = String.format(parameters[0].description, parameters.slice(1), formatters, formattedResult, append);
            formattedResult = result.formattedResult;
            parameters = result.unusedSubstitutions;
            if (parameters.length)
                formattedResult.appendChild(document.createTextNode(" "));
        }

        for (var i = 0; i < parameters.length; ++i) {
            if (Object.proxyType(parameters[i]) === "string")
                formattedResult.appendChild(WebInspector.linkifyStringAsFragment(parameters[i].description));
            else
                formattedResult.appendChild(formatForConsole(parameters[i]));

            if (i < parameters.length - 1)
                formattedResult.appendChild(document.createTextNode(" "));
        }

        return formattedResult;
    },

    toMessageElement: function()
    {
        if (this._element)
            return this._element;

        var element = document.createElement("div");
        element.message = this;
        element.className = "console-message";

        this._element = element;

        switch (this.source) {
            case WebInspector.ConsoleMessage.MessageSource.HTML:
                element.addStyleClass("console-html-source");
                break;
            case WebInspector.ConsoleMessage.MessageSource.WML:
                element.addStyleClass("console-wml-source");
                break;
            case WebInspector.ConsoleMessage.MessageSource.XML:
                element.addStyleClass("console-xml-source");
                break;
            case WebInspector.ConsoleMessage.MessageSource.JS:
                element.addStyleClass("console-js-source");
                break;
            case WebInspector.ConsoleMessage.MessageSource.CSS:
                element.addStyleClass("console-css-source");
                break;
            case WebInspector.ConsoleMessage.MessageSource.Other:
                element.addStyleClass("console-other-source");
                break;
        }

        switch (this.level) {
            case WebInspector.ConsoleMessage.MessageLevel.Tip:
                element.addStyleClass("console-tip-level");
                break;
            case WebInspector.ConsoleMessage.MessageLevel.Log:
                element.addStyleClass("console-log-level");
                break;
            case WebInspector.ConsoleMessage.MessageLevel.Debug:
                element.addStyleClass("console-debug-level");
                break;
            case WebInspector.ConsoleMessage.MessageLevel.Warning:
                element.addStyleClass("console-warning-level");
                break;
            case WebInspector.ConsoleMessage.MessageLevel.Error:
                element.addStyleClass("console-error-level");
                break;
        }
        
        if (this.type === WebInspector.ConsoleMessage.MessageType.StartGroup)
            element.addStyleClass("console-group-title");

        if (this.elementsTreeOutline) {
            element.addStyleClass("outline-disclosure");
            element.appendChild(this.elementsTreeOutline.element);
            return element;
        }

        if (this.url && this.url !== "undefined") {
            var urlElement = document.createElement("a");
            urlElement.className = "console-message-url webkit-html-resource-link";
            urlElement.href = this.url;
            urlElement.lineNumber = this.line;

            if (this.source === WebInspector.ConsoleMessage.MessageSource.JS)
                urlElement.preferredPanel = "scripts";

            if (this.line > 0)
                urlElement.textContent = WebInspector.displayNameForURL(this.url) + ":" + this.line;
            else
                urlElement.textContent = WebInspector.displayNameForURL(this.url);

            element.appendChild(urlElement);
        }

        var messageTextElement = document.createElement("span");
        messageTextElement.className = "console-message-text";
        if (this.type === WebInspector.ConsoleMessage.MessageType.Assert)
            messageTextElement.appendChild(document.createTextNode(WebInspector.UIString("Assertion failed: ")));
        messageTextElement.appendChild(this.formattedMessage);
        element.appendChild(messageTextElement);

        if (this.repeatCount > 1)
            this._updateRepeatCount();

        return element;
    },

    _updateRepeatCount: function() {
        if (!this.repeatCountElement) {
            this.repeatCountElement = document.createElement("span");
            this.repeatCountElement.className = "bubble";
    
            this._element.insertBefore(this.repeatCountElement, this._element.firstChild);
            this._element.addStyleClass("repeated-message");
        }
        this.repeatCountElement.textContent = this.repeatCount;
    },

    toString: function()
    {
        var sourceString;
        switch (this.source) {
            case WebInspector.ConsoleMessage.MessageSource.HTML:
                sourceString = "HTML";
                break;
            case WebInspector.ConsoleMessage.MessageSource.WML:
                sourceString = "WML";
                break;
            case WebInspector.ConsoleMessage.MessageSource.XML:
                sourceString = "XML";
                break;
            case WebInspector.ConsoleMessage.MessageSource.JS:
                sourceString = "JS";
                break;
            case WebInspector.ConsoleMessage.MessageSource.CSS:
                sourceString = "CSS";
                break;
            case WebInspector.ConsoleMessage.MessageSource.Other:
                sourceString = "Other";
                break;
        }

        var typeString;
        switch (this.type) {
            case WebInspector.ConsoleMessage.MessageType.Log:
                typeString = "Log";
                break;
            case WebInspector.ConsoleMessage.MessageType.Object:
                typeString = "Object";
                break;
            case WebInspector.ConsoleMessage.MessageType.Trace:
                typeString = "Trace";
                break;
            case WebInspector.ConsoleMessage.MessageType.StartGroup:
                typeString = "Start Group";
                break;
            case WebInspector.ConsoleMessage.MessageType.EndGroup:
                typeString = "End Group";
                break;
            case WebInspector.ConsoleMessage.MessageType.Assert:
                typeString = "Assert";
                break;
        }
        
        var levelString;
        switch (this.level) {
            case WebInspector.ConsoleMessage.MessageLevel.Tip:
                levelString = "Tip";
                break;
            case WebInspector.ConsoleMessage.MessageLevel.Log:
                levelString = "Log";
                break;
            case WebInspector.ConsoleMessage.MessageLevel.Warning:
                levelString = "Warning";
                break;
            case WebInspector.ConsoleMessage.MessageLevel.Debug:
                levelString = "Debug";
                break;
            case WebInspector.ConsoleMessage.MessageLevel.Error:
                levelString = "Error";
                break;
        }

        return sourceString + " " + typeString + " " + levelString + ": " + this.formattedMessage.textContent + "\n" + this.url + " line " + this.line;
    },

    isEqual: function(msg, disreguardGroup)
    {
        if (!msg)
            return false;

        var ret = (this.source == msg.source)
            && (this.type == msg.type)
            && (this.level == msg.level)
            && (this.line == msg.line)
            && (this.url == msg.url)
            && (this.message == msg.message);

        return (disreguardGroup ? ret : (ret && (this.groupLevel == msg.groupLevel)));
    }
}

// Note: Keep these constants in sync with the ones in Console.h
WebInspector.ConsoleMessage.MessageSource = {
    HTML: 0,
    WML: 1,
    XML: 2,
    JS: 3,
    CSS: 4,
    Other: 5
}

WebInspector.ConsoleMessage.MessageType = {
    Log: 0,
    Object: 1,
    Trace: 2,
    StartGroup: 3,
    EndGroup: 4,
    Assert: 5
}

WebInspector.ConsoleMessage.MessageLevel = {
    Tip: 0,
    Log: 1,
    Warning: 2,
    Error: 3,
    Debug: 4
}

WebInspector.ConsoleCommand = function(command)
{
    this.command = command;
}

WebInspector.ConsoleCommand.prototype = {
    toMessageElement: function()
    {
        var element = document.createElement("div");
        element.command = this;
        element.className = "console-user-command";

        var commandTextElement = document.createElement("span");
        commandTextElement.className = "console-message-text";
        commandTextElement.textContent = this.command;
        element.appendChild(commandTextElement);

        return element;
    }
}

WebInspector.ConsoleTextMessage = function(text, level)
{
    level = level || WebInspector.ConsoleMessage.MessageLevel.Log;
    WebInspector.ConsoleMessage.call(this, WebInspector.ConsoleMessage.MessageSource.JS, WebInspector.ConsoleMessage.MessageType.Log, level, 0, null, null, 1, text);
}

WebInspector.ConsoleTextMessage.prototype.__proto__ = WebInspector.ConsoleMessage.prototype;

WebInspector.ConsoleCommandResult = function(result, exception, originatingCommand)
{
    var level = (exception ? WebInspector.ConsoleMessage.MessageLevel.Error : WebInspector.ConsoleMessage.MessageLevel.Log);
    var message = result;
    var line = (exception ? result.line : -1);
    var url = (exception ? result.sourceURL : null);

    WebInspector.ConsoleMessage.call(this, WebInspector.ConsoleMessage.MessageSource.JS, WebInspector.ConsoleMessage.MessageType.Log, level, line, url, null, 1, message);

    this.originatingCommand = originatingCommand;
}

WebInspector.ConsoleCommandResult.prototype = {
    toMessageElement: function()
    {
        var element = WebInspector.ConsoleMessage.prototype.toMessageElement.call(this);
        element.addStyleClass("console-user-command-result");
        return element;
    }
}

WebInspector.ConsoleCommandResult.prototype.__proto__ = WebInspector.ConsoleMessage.prototype;

WebInspector.ConsoleGroup = function(parentGroup, level)
{
    this.parentGroup = parentGroup;
    this.level = level;

    var element = document.createElement("div");
    element.className = "console-group";
    element.group = this;
    this.element = element;

    var messagesElement = document.createElement("div");
    messagesElement.className = "console-group-messages";
    element.appendChild(messagesElement);
    this.messagesElement = messagesElement;
}

WebInspector.ConsoleGroup.prototype = {
    addMessage: function(msg)
    {
        var element = msg.toMessageElement();
        
        if (msg.type === WebInspector.ConsoleMessage.MessageType.StartGroup) {
            this.messagesElement.parentNode.insertBefore(element, this.messagesElement);
            element.addEventListener("click", this._titleClicked.bind(this), true);
        } else
            this.messagesElement.appendChild(element);

        if (element.previousSibling && msg.originatingCommand && element.previousSibling.command === msg.originatingCommand)
            element.previousSibling.addStyleClass("console-adjacent-user-command-result");
    },

    _titleClicked: function(event)
    {
        var groupTitleElement = event.target.enclosingNodeOrSelfWithClass("console-group-title");
        if (groupTitleElement) {
            var groupElement = groupTitleElement.enclosingNodeOrSelfWithClass("console-group");
            if (groupElement)
                if (groupElement.hasStyleClass("collapsed"))
                    groupElement.removeStyleClass("collapsed");
                else
                    groupElement.addStyleClass("collapsed");
            groupTitleElement.scrollIntoViewIfNeeded(true);
        }

        event.stopPropagation();
        event.preventDefault();
    }
}

/* Panel.js */

/*
 * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.Panel = function()
{
    WebInspector.View.call(this);

    this.element.addStyleClass("panel");
}

WebInspector.Panel.prototype = {
    get toolbarItem()
    {
        if (this._toolbarItem)
            return this._toolbarItem;

        // Sample toolbar item as markup:
        // <button class="toolbar-item resources toggleable">
        // <div class="toolbar-icon"></div>
        // <div class="toolbar-label">Resources</div>
        // </button>

        this._toolbarItem = document.createElement("button");
        this._toolbarItem.className = "toolbar-item toggleable";
        this._toolbarItem.panel = this;

        if ("toolbarItemClass" in this)
            this._toolbarItem.addStyleClass(this.toolbarItemClass);

        var iconElement = document.createElement("div");
        iconElement.className = "toolbar-icon";
        this._toolbarItem.appendChild(iconElement);

        if ("toolbarItemLabel" in this) {
            var labelElement = document.createElement("div");
            labelElement.className = "toolbar-label";
            labelElement.textContent = this.toolbarItemLabel;
            this._toolbarItem.appendChild(labelElement);
        }

        return this._toolbarItem;
    },

    show: function()
    {
        WebInspector.View.prototype.show.call(this);

        var statusBarItems = this.statusBarItems;
        if (statusBarItems) {
            this._statusBarItemContainer = document.createElement("div");
            for (var i = 0; i < statusBarItems.length; ++i)
                this._statusBarItemContainer.appendChild(statusBarItems[i]);
            document.getElementById("main-status-bar").appendChild(this._statusBarItemContainer);
        }

        if ("_toolbarItem" in this)
            this._toolbarItem.addStyleClass("toggled-on");

        WebInspector.currentFocusElement = document.getElementById("main-panels");

        this.updateSidebarWidth();
    },

    hide: function()
    {
        WebInspector.View.prototype.hide.call(this);

        if (this._statusBarItemContainer && this._statusBarItemContainer.parentNode)
            this._statusBarItemContainer.parentNode.removeChild(this._statusBarItemContainer);
        delete this._statusBarItemContainer;
        if ("_toolbarItem" in this)
            this._toolbarItem.removeStyleClass("toggled-on");
    },

    attach: function()
    {
        if (!this.element.parentNode)
            document.getElementById("main-panels").appendChild(this.element);
    },

    searchCanceled: function(startingNewSearch)
    {
        if (this._searchResults) {
            for (var i = 0; i < this._searchResults.length; ++i) {
                var view = this._searchResults[i];
                if (view.searchCanceled)
                    view.searchCanceled();
                delete view.currentQuery;
            }
        }

        WebInspector.updateSearchMatchesCount(0, this);

        if (this._currentSearchChunkIntervalIdentifier) {
            clearInterval(this._currentSearchChunkIntervalIdentifier);
            delete this._currentSearchChunkIntervalIdentifier;
        }

        this._totalSearchMatches = 0;
        this._currentSearchResultIndex = 0;
        this._searchResults = [];
    },

    performSearch: function(query)
    {
        // Call searchCanceled since it will reset everything we need before doing a new search.
        this.searchCanceled(true);

        var searchableViews = this.searchableViews;
        if (!searchableViews || !searchableViews.length)
            return;

        var parentElement = this.viewsContainerElement;
        var visibleView = this.visibleView;
        var sortFuction = this.searchResultsSortFunction;

        var matchesCountUpdateTimeout = null;

        function updateMatchesCount()
        {
            WebInspector.updateSearchMatchesCount(this._totalSearchMatches, this);
            matchesCountUpdateTimeout = null;
        }

        function updateMatchesCountSoon()
        {
            if (matchesCountUpdateTimeout)
                return;
            // Update the matches count every half-second so it doesn't feel twitchy.
            matchesCountUpdateTimeout = setTimeout(updateMatchesCount.bind(this), 500);
        }

        function finishedCallback(view, searchMatches)
        {
            if (!searchMatches)
                return;

            this._totalSearchMatches += searchMatches;
            this._searchResults.push(view);

            if (sortFuction)
                this._searchResults.sort(sortFuction);

            if (this.searchMatchFound)
                this.searchMatchFound(view, searchMatches);

            updateMatchesCountSoon.call(this);

            if (view === visibleView)
                view.jumpToFirstSearchResult();
        }

        var i = 0;
        var panel = this;
        var boundFinishedCallback = finishedCallback.bind(this);
        var chunkIntervalIdentifier = null;

        // Split up the work into chunks so we don't block the
        // UI thread while processing.

        function processChunk()
        {
            var view = searchableViews[i];

            if (++i >= searchableViews.length) {
                if (panel._currentSearchChunkIntervalIdentifier === chunkIntervalIdentifier)
                    delete panel._currentSearchChunkIntervalIdentifier;
                clearInterval(chunkIntervalIdentifier);
            }

            if (!view)
                return;

            if (view.element.parentNode !== parentElement && view.element.parentNode && parentElement)
                view.detach();

            view.currentQuery = query;
            view.performSearch(query, boundFinishedCallback);
        }

        processChunk();

        chunkIntervalIdentifier = setInterval(processChunk, 25);
        this._currentSearchChunkIntervalIdentifier = chunkIntervalIdentifier;
    },

    jumpToNextSearchResult: function()
    {
        if (!this.showView || !this._searchResults || !this._searchResults.length)
            return;

        var showFirstResult = false;

        this._currentSearchResultIndex = this._searchResults.indexOf(this.visibleView);
        if (this._currentSearchResultIndex === -1) {
            this._currentSearchResultIndex = 0;
            showFirstResult = true;
        }

        var currentView = this._searchResults[this._currentSearchResultIndex];

        if (currentView.showingLastSearchResult()) {
            if (++this._currentSearchResultIndex >= this._searchResults.length)
                this._currentSearchResultIndex = 0;
            currentView = this._searchResults[this._currentSearchResultIndex];
            showFirstResult = true;
        }

        if (currentView !== this.visibleView) {
            currentView = this.visibleView;
            this._currentSearchResultIndex = 0;
            showFirstResult = true;
        }

        if (showFirstResult)
            currentView.jumpToFirstSearchResult();
        else
            currentView.jumpToNextSearchResult();
    },

    jumpToPreviousSearchResult: function()
    {
        if (!this.showView || !this._searchResults || !this._searchResults.length)
            return;

        var showLastResult = false;

        this._currentSearchResultIndex = this._searchResults.indexOf(this.visibleView);
        if (this._currentSearchResultIndex === -1) {
            this._currentSearchResultIndex = 0;
            showLastResult = true;
        }

        var currentView = this._searchResults[this._currentSearchResultIndex];

        if (currentView.showingFirstSearchResult()) {
            if (--this._currentSearchResultIndex < 0)
                this._currentSearchResultIndex = (this._searchResults.length - 1);
            currentView = this._searchResults[this._currentSearchResultIndex];
            showLastResult = true;
        }

        if (currentView !== this.visibleView)
            this.showView(currentView);

        if (showLastResult)
            currentView.jumpToLastSearchResult();
        else
            currentView.jumpToPreviousSearchResult();
    },

    handleKeyEvent: function(event)
    {
        this.handleSidebarKeyEvent(event);
    },

    handleSidebarKeyEvent: function(event)
    {
        if (this.hasSidebar && this.sidebarTree)
            this.sidebarTree.handleKeyEvent(event);
    },

    createSidebar: function(parentElement, resizerParentElement)
    {
        if (this.hasSidebar)
            return;

        if (!parentElement)
            parentElement = this.element;

        if (!resizerParentElement)
            resizerParentElement = parentElement;

        this.hasSidebar = true;

        this.sidebarElement = document.createElement("div");
        this.sidebarElement.className = "sidebar";
        parentElement.appendChild(this.sidebarElement);

        this.sidebarResizeElement = document.createElement("div");
        this.sidebarResizeElement.className = "sidebar-resizer-vertical";
        this.sidebarResizeElement.addEventListener("mousedown", this._startSidebarDragging.bind(this), false);
        resizerParentElement.appendChild(this.sidebarResizeElement);

        this.sidebarTreeElement = document.createElement("ol");
        this.sidebarTreeElement.className = "sidebar-tree";
        this.sidebarElement.appendChild(this.sidebarTreeElement);

        this.sidebarTree = new TreeOutline(this.sidebarTreeElement);
    },

    _startSidebarDragging: function(event)
    {
        WebInspector.elementDragStart(this.sidebarResizeElement, this._sidebarDragging.bind(this), this._endSidebarDragging.bind(this), event, "col-resize");
    },

    _sidebarDragging: function(event)
    {
        this.updateSidebarWidth(event.pageX);

        event.preventDefault();
    },

    _endSidebarDragging: function(event)
    {
        WebInspector.elementDragEnd(event);
    },

    updateSidebarWidth: function(width)
    {
        if (!this.hasSidebar)
            return;

        if (this.sidebarElement.offsetWidth <= 0) {
            // The stylesheet hasn't loaded yet or the window is closed,
            // so we can't calculate what is need. Return early.
            return;
        }

        if (!("_currentSidebarWidth" in this))
            this._currentSidebarWidth = this.sidebarElement.offsetWidth;

        if (typeof width === "undefined")
            width = this._currentSidebarWidth;

        width = Number.constrain(width, Preferences.minSidebarWidth, window.innerWidth / 2);

        this._currentSidebarWidth = width;
        this.setSidebarWidth(width);

        this.updateMainViewWidth(width);

        var visibleView = this.visibleView;
        if (visibleView && "resize" in visibleView)
            visibleView.resize();
    },

    setSidebarWidth: function(width)
    {
        this.sidebarElement.style.width = width + "px";
        this.sidebarResizeElement.style.left = (width - 3) + "px";
    },

    updateMainViewWidth: function(width)
    {
        // Should be implemented by ancestors.
    }
}

WebInspector.Panel.prototype.__proto__ = WebInspector.View.prototype;

/* AbstractTimelinePanel.js */

/*
 * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
 * Copyright (C) 2008, 2009 Anthony Ricaud <rik@webkit.org>
 * Copyright (C) 2009 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.AbstractTimelinePanel = function()
{
    WebInspector.Panel.call(this);
    this._items = [];
    this._staleItems = [];
}

WebInspector.AbstractTimelinePanel.prototype = {
    get categories()
    {
        // Should be implemented by the concrete subclasses.
        return {};
    },

    populateSidebar: function()
    {
        // Should be implemented by the concrete subclasses.
    },

    createItemTreeElement: function(item)
    {
        // Should be implemented by the concrete subclasses.
    },

    createItemGraph: function(item)
    {
        // Should be implemented by the concrete subclasses.
    },

    get items()
    {
        return this._items;
    },

    createInterface: function()
    {
        this.containerElement = document.createElement("div");
        this.containerElement.id = "resources-container";
        this.containerElement.addEventListener("scroll", this._updateDividersLabelBarPosition.bind(this), false);
        this.element.appendChild(this.containerElement);

        this.createSidebar(this.containerElement, this.element);
        this.sidebarElement.id = "resources-sidebar";
        this.populateSidebar();

        this._containerContentElement = document.createElement("div");
        this._containerContentElement.id = "resources-container-content";
        this.containerElement.appendChild(this._containerContentElement);

        this.summaryBar = new WebInspector.SummaryBar(this.categories);
        this.summaryBar.element.id = "resources-summary";
        this._containerContentElement.appendChild(this.summaryBar.element);

        this._timelineGrid = new WebInspector.TimelineGrid(this._containerContentElement);
        this.itemsGraphsElement = this._timelineGrid.itemsGraphsElement;
    },

    createFilterPanel: function()
    {
        this.filterBarElement = document.createElement("div");
        this.filterBarElement.id = "resources-filter";
        this.filterBarElement.className = "scope-bar";
        this.element.appendChild(this.filterBarElement);

        function createFilterElement(category)
        {
            if (category === "all")
                var label = WebInspector.UIString("All");
            else if (this.categories[category])
                var label = this.categories[category].title;

            var categoryElement = document.createElement("li");
            categoryElement.category = category;
            categoryElement.addStyleClass(category);
            categoryElement.appendChild(document.createTextNode(label));
            categoryElement.addEventListener("click", this._updateFilter.bind(this), false);
            this.filterBarElement.appendChild(categoryElement);

            return categoryElement;
        }

        this.filterAllElement = createFilterElement.call(this, "all");

        // Add a divider
        var dividerElement = document.createElement("div");
        dividerElement.addStyleClass("divider");
        this.filterBarElement.appendChild(dividerElement);

        for (var category in this.categories)
            createFilterElement.call(this, category);
    },

    showCategory: function(category)
    {
        var filterClass = "filter-" + category.toLowerCase();
        this.itemsGraphsElement.addStyleClass(filterClass);
        this.itemsTreeElement.childrenListElement.addStyleClass(filterClass);
    },

    hideCategory: function(category)
    {
        var filterClass = "filter-" + category.toLowerCase();
        this.itemsGraphsElement.removeStyleClass(filterClass);
        this.itemsTreeElement.childrenListElement.removeStyleClass(filterClass);
    },

    filter: function(target, selectMultiple)
    {
        function unselectAll()
        {
            for (var i = 0; i < this.filterBarElement.childNodes.length; ++i) {
                var child = this.filterBarElement.childNodes[i];
                if (!child.category)
                    continue;

                child.removeStyleClass("selected");
                this.hideCategory(child.category);
            }
        }

        if (target === this.filterAllElement) {
            if (target.hasStyleClass("selected")) {
                // We can't unselect All, so we break early here
                return;
            }

            // If All wasn't selected, and now is, unselect everything else.
            unselectAll.call(this);
        } else {
            // Something other than All is being selected, so we want to unselect All.
            if (this.filterAllElement.hasStyleClass("selected")) {
                this.filterAllElement.removeStyleClass("selected");
                this.hideCategory("all");
            }
        }

        if (!selectMultiple) {
            // If multiple selection is off, we want to unselect everything else
            // and just select ourselves.
            unselectAll.call(this);

            target.addStyleClass("selected");
            this.showCategory(target.category);
            return;
        }

        if (target.hasStyleClass("selected")) {
            // If selectMultiple is turned on, and we were selected, we just
            // want to unselect ourselves.
            target.removeStyleClass("selected");
            this.hideCategory(target.category);
        } else {
            // If selectMultiple is turned on, and we weren't selected, we just
            // want to select ourselves.
            target.addStyleClass("selected");
            this.showCategory(target.category);
        }
    },

    _updateFilter: function(e)
    {
        var isMac = InspectorController.platform().indexOf("mac-") === 0;
        var selectMultiple = false;
        if (isMac && e.metaKey && !e.ctrlKey && !e.altKey && !e.shiftKey)
            selectMultiple = true;
        if (!isMac && e.ctrlKey && !e.metaKey && !e.altKey && !e.shiftKey)
            selectMultiple = true;

        this.filter(e.target, selectMultiple);

        // When we are updating our filtering, scroll to the top so we don't end up
        // in blank graph under all the resources.
        this.containerElement.scrollTop = 0;
    },

    updateGraphDividersIfNeeded: function(force)
    {
        if (!this.visible) {
            this.needsRefresh = true;
            return false;
        }
        return this._timelineGrid.updateDividers(force, this.calculator);
    },

    _updateDividersLabelBarPosition: function()
    {
        var scrollTop = this.containerElement.scrollTop;
        var dividersTop = (scrollTop < this.summaryBar.element.offsetHeight ? this.summaryBar.element.offsetHeight : scrollTop);
        this._timelineGrid.setScrollAndDividerTop(scrollTop, dividersTop);
    },

    get needsRefresh()
    {
        return this._needsRefresh;
    },

    set needsRefresh(x)
    {
        if (this._needsRefresh === x)
            return;

        this._needsRefresh = x;

        if (x) {
            if (this.visible && !("_refreshTimeout" in this))
                this._refreshTimeout = setTimeout(this.refresh.bind(this), 500);
        } else {
            if ("_refreshTimeout" in this) {
                clearTimeout(this._refreshTimeout);
                delete this._refreshTimeout;
            }
        }
    },

    refreshIfNeeded: function()
    {
        if (this.needsRefresh)
            this.refresh();
    },

    show: function()
    {
        WebInspector.Panel.prototype.show.call(this);

        this._updateDividersLabelBarPosition();
        this.refreshIfNeeded();
    },

    resize: function()
    {
        this.updateGraphDividersIfNeeded();
    },

    updateMainViewWidth: function(width)
    {
        this._containerContentElement.style.left = width + "px";
        this.updateGraphDividersIfNeeded();
    },

    invalidateAllItems: function()
    {
        this._staleItems = this._items;
    },

    refresh: function()
    {
        this.needsRefresh = false;

        var staleItemsLength = this._staleItems.length;

        var boundariesChanged = false;

        for (var i = 0; i < staleItemsLength; ++i) {
            var item = this._staleItems[i];
            if (!item._itemsTreeElement) {
                // Create the timeline tree element and graph.
                item._itemsTreeElement = this.createItemTreeElement(item);
                item._itemsTreeElement._itemGraph = this.createItemGraph(item);

                this.itemsTreeElement.appendChild(item._itemsTreeElement);
                this.itemsGraphsElement.appendChild(item._itemsTreeElement._itemGraph.graphElement);
            }

            if (item._itemsTreeElement.refresh)
                item._itemsTreeElement.refresh();

            if (this.calculator.updateBoundaries(item))
                boundariesChanged = true;
        }

        if (boundariesChanged) {
            // The boundaries changed, so all item graphs are stale.
            this._staleItems = this._items;
            staleItemsLength = this._staleItems.length;
        }

        for (var i = 0; i < staleItemsLength; ++i)
            this._staleItems[i]._itemsTreeElement._itemGraph.refresh(this.calculator);

        this._staleItems = [];

        this.updateGraphDividersIfNeeded();
    },

    reset: function()
    {
        this.containerElement.scrollTop = 0;

        if (this._calculator)
            this._calculator.reset();

        if (this._items) {
            var itemsLength = this._items.length;
            for (var i = 0; i < itemsLength; ++i) {
                var item = this._items[i];
                delete item._itemsTreeElement;
            }
        }

        this._items = [];
        this._staleItems = [];

        this.itemsTreeElement.removeChildren();
        this.itemsGraphsElement.removeChildren();

        this.updateGraphDividersIfNeeded(true);
    },

    get calculator()
    {
        return this._calculator;
    },

    set calculator(x)
    {
        if (!x || this._calculator === x)
            return;

        this._calculator = x;
        this._calculator.reset();

        this._staleItems = this._items;
        this.refresh();
    },

    addItem: function(item)
    {
        this._items.push(item);
        this.refreshItem(item);
    },

    removeItem: function(item)
    {
        this._items.remove(item, true);

        if (item._itemsTreeElement) {
            this.itemsTreeElement.removeChild(item._itemsTreeElement);
            this.itemsGraphsElement.removeChild(item._itemsTreeElement._itemGraph.graphElement);
        }

        delete item._itemsTreeElement;
        this.adjustScrollPosition();
    },

    refreshItem: function(item)
    {
        this._staleItems.push(item);
        this.needsRefresh = true;
    },

    revealAndSelectItem: function(item)
    {
        if (item._itemsTreeElement) {
            item._itemsTreeElement.reveal();
            item._itemsTreeElement.select(true);
        }
    },

    sortItems: function(sortingFunction)
    {
        var sortedElements = [].concat(this.itemsTreeElement.children);
        sortedElements.sort(sortingFunction);

        var sortedElementsLength = sortedElements.length;
        for (var i = 0; i < sortedElementsLength; ++i) {
            var treeElement = sortedElements[i];
            if (treeElement === this.itemsTreeElement.children[i])
                continue;

            var wasSelected = treeElement.selected;
            this.itemsTreeElement.removeChild(treeElement);
            this.itemsTreeElement.insertChild(treeElement, i);
            if (wasSelected)
                treeElement.select(true);

            var graphElement = treeElement._itemGraph.graphElement;
            this.itemsGraphsElement.insertBefore(graphElement, this.itemsGraphsElement.children[i]);
        }
    },

    adjustScrollPosition: function()
    {
        // Prevent the container from being scrolled off the end.
        if ((this.containerElement.scrollTop + this.containerElement.offsetHeight) > this.sidebarElement.offsetHeight)
            this.containerElement.scrollTop = (this.sidebarElement.offsetHeight - this.containerElement.offsetHeight);
    },

    addEventDivider: function(divider)
    {
        this._timelineGrid.addEventDivider(divider);
    }
}

WebInspector.AbstractTimelinePanel.prototype.__proto__ = WebInspector.Panel.prototype;

WebInspector.AbstractTimelineCalculator = function()
{
}

WebInspector.AbstractTimelineCalculator.prototype = {
    computeSummaryValues: function(items)
    {
        var total = 0;
        var categoryValues = {};

        var itemsLength = items.length;
        for (var i = 0; i < itemsLength; ++i) {
            var item = items[i];
            var value = this._value(item);
            if (typeof value === "undefined")
                continue;
            if (!(item.category.name in categoryValues))
                categoryValues[item.category.name] = 0;
            categoryValues[item.category.name] += value;
            total += value;
        }

        return {categoryValues: categoryValues, total: total};
    },

    computeBarGraphPercentages: function(item)
    {
        return {start: 0, middle: 0, end: (this._value(item) / this.boundarySpan) * 100};
    },

    computeBarGraphLabels: function(item)
    {
        const label = this.formatValue(this._value(item));
        return {left: label, right: label, tooltip: label};
    },

    get boundarySpan()
    {
        return this.maximumBoundary - this.minimumBoundary;
    },

    updateBoundaries: function(item)
    {
        this.minimumBoundary = 0;

        var value = this._value(item);
        if (typeof this.maximumBoundary === "undefined" || value > this.maximumBoundary) {
            this.maximumBoundary = value;
            return true;
        }
        return false;
    },

    reset: function()
    {
        delete this.minimumBoundary;
        delete this.maximumBoundary;
    },

    _value: function(item)
    {
        return 0;
    },

    formatValue: function(value)
    {
        return value.toString();
    }
}

WebInspector.AbstractTimelineCategory = function(name, title, color)
{
    this.name = name;
    this.title = title;
    this.color = color;
}

WebInspector.AbstractTimelineCategory.prototype = {
    toString: function()
    {
        return this.title;
    }
}

WebInspector.TimelineGrid = function(container)
{
    this._itemsGraphsElement = document.createElement("div");
    this._itemsGraphsElement.id = "resources-graphs";
    container.appendChild(this._itemsGraphsElement);

    this._dividersElement = document.createElement("div");
    this._dividersElement.id = "resources-dividers";
    container.appendChild(this._dividersElement);

    this._eventDividersElement = document.createElement("div");
    this._eventDividersElement.id = "resources-event-dividers";
    container.appendChild(this._eventDividersElement);

    this._dividersLabelBarElement = document.createElement("div");
    this._dividersLabelBarElement.id = "resources-dividers-label-bar";
    container.appendChild(this._dividersLabelBarElement);
}

WebInspector.TimelineGrid.prototype = {
    get itemsGraphsElement()
    {
        return this._itemsGraphsElement;
    },

    updateDividers: function(force, calculator)
    {
        var dividerCount = Math.round(this._dividersElement.offsetWidth / 64);
        var slice = calculator.boundarySpan / dividerCount;
        if (!force && this._currentDividerSlice === slice)
            return false;

        this._currentDividerSlice = slice;

        this._dividersElement.removeChildren();
        this._eventDividersElement.removeChildren();
        this._dividersLabelBarElement.removeChildren();

        for (var i = 1; i <= dividerCount; ++i) {
            var divider = document.createElement("div");
            divider.className = "resources-divider";
            if (i === dividerCount)
                divider.addStyleClass("last");
            divider.style.left = ((i / dividerCount) * 100) + "%";

            this._dividersElement.appendChild(divider.cloneNode());

            var label = document.createElement("div");
            label.className = "resources-divider-label";
            if (!isNaN(slice))
                label.textContent = calculator.formatValue(slice * i);
            divider.appendChild(label);

            this._dividersLabelBarElement.appendChild(divider);
        }
        return true;
    },

    addEventDivider: function(divider)
    {
        this._eventDividersElement.appendChild(divider);
    },

    setScrollAndDividerTop: function(scrollTop, dividersTop)
    {
        this._dividersElement.style.top = scrollTop + "px";
        this._eventDividersElement.style.top = scrollTop + "px";
        this._dividersLabelBarElement.style.top = dividersTop + "px";
    }
}

/* Resource.js */

/*
 * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer. 
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution. 
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission. 
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.Resource = function(requestHeaders, url, domain, path, lastPathComponent, identifier, mainResource, cached, requestMethod, requestFormData)
{
    this.identifier = identifier;

    this.startTime = -1;
    this.endTime = -1;
    this.mainResource = mainResource;
    this.requestHeaders = requestHeaders;
    this.url = url;
    this.domain = domain;
    this.path = path;
    this.lastPathComponent = lastPathComponent;
    this.cached = cached;
    this.requestMethod = requestMethod || "";
    this.requestFormData = requestFormData || "";

    this.category = WebInspector.resourceCategories.other;
}


WebInspector.Resource.StatusText = {
    100: "Continue",
    101: "Switching Protocols",
    102: "Processing (WebDav)",
    200: "OK",
    201: "Created",
    202: "Accepted",
    203: "Non-Authoritative Information",
    204: "No Content",
    205: "Reset Content",
    206: "Partial Content",
    207: "Multi-Status (WebDav)",
    300: "Multiple Choices",
    301: "Moved Permanently",
    302: "Found",
    303: "See Other",
    304: "Not Modified",
    305: "Use Proxy",
    306: "Switch Proxy",
    307: "Temporary",
    400: "Bad Request",
    401: "Unauthorized",
    402: "Payment Required",
    403: "Forbidden",
    404: "Not Found",
    405: "Method Not Allowed",
    406: "Not Acceptable",
    407: "Proxy Authentication Required",
    408: "Request Timeout",
    409: "Conflict",
    410: "Gone",
    411: "Length Required",
    412: "Precondition Failed",
    413: "Request Entity Too Large",
    414: "Request-URI Too Long",
    415: "Unsupported Media Type",
    416: "Requested Range Not Satisfiable",
    417: "Expectation Failed",
    418: "I'm a teapot",
    422: "Unprocessable Entity (WebDav)",
    423: "Locked (WebDav)",
    424: "Failed Dependency (WebDav)",
    425: "Unordered Collection",
    426: "Upgrade Required",
    449: "Retry With",
    500: "Internal Server Error",
    501: "Not Implemented",
    502: "Bad Gateway",
    503: "Service Unavailable",
    504: "Gateway Timeout",
    505: "HTTP Version Not Supported",
    506: "Variant Also Negotiates",
    507: "Insufficient Storage (WebDav)",
    509: "Bandwidth Limit Exceeded",
    510: "Not Extended"
};

// Keep these in sync with WebCore::InspectorResource::Type
WebInspector.Resource.Type = {
    Document:   0,
    Stylesheet: 1,
    Image:      2,
    Font:       3,
    Script:     4,
    XHR:        5,
    Other:      6,

    isTextType: function(type)
    {
        return (type === this.Document) || (type === this.Stylesheet) || (type === this.Script) || (type === this.XHR);
    },

    toString: function(type)
    {
        switch (type) {
            case this.Document:
                return WebInspector.UIString("document");
            case this.Stylesheet:
                return WebInspector.UIString("stylesheet");
            case this.Image:
                return WebInspector.UIString("image");
            case this.Font:
                return WebInspector.UIString("font");
            case this.Script:
                return WebInspector.UIString("script");
            case this.XHR:
                return WebInspector.UIString("XHR");
            case this.Other:
            default:
                return WebInspector.UIString("other");
        }
    }
}

WebInspector.Resource.prototype = {
    get url()
    {
        return this._url;
    },

    set url(x)
    {
        if (this._url === x)
            return;

        var oldURL = this._url;
        this._url = x;

        // FIXME: We should make the WebInspector object listen for the "url changed" event.
        // Then resourceURLChanged can be removed.
        WebInspector.resourceURLChanged(this, oldURL);

        this.dispatchEventToListeners("url changed");
    },

    get domain()
    {
        return this._domain;
    },

    set domain(x)
    {
        if (this._domain === x)
            return;
        this._domain = x;
    },

    get lastPathComponent()
    {
        return this._lastPathComponent;
    },

    set lastPathComponent(x)
    {
        if (this._lastPathComponent === x)
            return;
        this._lastPathComponent = x;
        this._lastPathComponentLowerCase = x ? x.toLowerCase() : null;
    },

    get displayName()
    {
        var title = this.lastPathComponent;
        if (!title)
            title = this.displayDomain;
        if (!title && this.url)
            title = this.url.trimURL(WebInspector.mainResource ? WebInspector.mainResource.domain : "");
        if (title === "/")
            title = this.url;
        return title;
    },

    get displayDomain()
    {
        // WebInspector.Database calls this, so don't access more than this.domain.
        if (this.domain && (!WebInspector.mainResource || (WebInspector.mainResource && this.domain !== WebInspector.mainResource.domain)))
            return this.domain;
        return "";
    },

    get startTime()
    {
        return this._startTime || -1;
    },

    set startTime(x)
    {
        if (this._startTime === x)
            return;

        this._startTime = x;

        if (WebInspector.panels.resources)
            WebInspector.panels.resources.refreshResource(this);
    },

    get responseReceivedTime()
    {
        return this._responseReceivedTime || -1;
    },

    set responseReceivedTime(x)
    {
        if (this._responseReceivedTime === x)
            return;

        this._responseReceivedTime = x;

        if (WebInspector.panels.resources)
            WebInspector.panels.resources.refreshResource(this);
    },

    get endTime()
    {
        return this._endTime || -1;
    },

    set endTime(x)
    {
        if (this._endTime === x)
            return;

        this._endTime = x;

        if (WebInspector.panels.resources)
            WebInspector.panels.resources.refreshResource(this);
    },

    get duration()
    {
        if (this._endTime === -1 || this._startTime === -1)
            return -1;
        return this._endTime - this._startTime;
    },

    get latency()
    {
        if (this._responseReceivedTime === -1 || this._startTime === -1)
            return -1;
        return this._responseReceivedTime - this._startTime;
    },

    get contentLength()
    {
        return this._contentLength || 0;
    },

    set contentLength(x)
    {
        if (this._contentLength === x)
            return;

        this._contentLength = x;

        if (WebInspector.panels.resources)
            WebInspector.panels.resources.refreshResource(this);
    },

    get expectedContentLength()
    {
        return this._expectedContentLength || 0;
    },

    set expectedContentLength(x)
    {
        if (this._expectedContentLength === x)
            return;
        this._expectedContentLength = x;
    },

    get finished()
    {
        return this._finished;
    },

    set finished(x)
    {
        if (this._finished === x)
            return;

        this._finished = x;

        if (x) {
            this._checkTips();
            this._checkWarnings();
            this.dispatchEventToListeners("finished");
        }
    },

    get failed()
    {
        return this._failed;
    },

    set failed(x)
    {
        this._failed = x;
    },

    get category()
    {
        return this._category;
    },

    set category(x)
    {
        if (this._category === x)
            return;

        var oldCategory = this._category;
        if (oldCategory)
            oldCategory.removeResource(this);

        this._category = x;

        if (this._category)
            this._category.addResource(this);

        if (WebInspector.panels.resources) {
            WebInspector.panels.resources.refreshResource(this);
            WebInspector.panels.resources.recreateViewForResourceIfNeeded(this);
        }
    },

    get mimeType()
    {
        return this._mimeType;
    },

    set mimeType(x)
    {
        if (this._mimeType === x)
            return;

        this._mimeType = x;
    },

    get type()
    {
        return this._type;
    },

    set type(x)
    {
        if (this._type === x)
            return;

        this._type = x;

        switch (x) {
            case WebInspector.Resource.Type.Document:
                this.category = WebInspector.resourceCategories.documents;
                break;
            case WebInspector.Resource.Type.Stylesheet:
                this.category = WebInspector.resourceCategories.stylesheets;
                break;
            case WebInspector.Resource.Type.Script:
                this.category = WebInspector.resourceCategories.scripts;
                break;
            case WebInspector.Resource.Type.Image:
                this.category = WebInspector.resourceCategories.images;
                break;
            case WebInspector.Resource.Type.Font:
                this.category = WebInspector.resourceCategories.fonts;
                break;
            case WebInspector.Resource.Type.XHR:
                this.category = WebInspector.resourceCategories.xhr;
                break;
            case WebInspector.Resource.Type.Other:
            default:
                this.category = WebInspector.resourceCategories.other;
                break;
        }
    },

    get requestHeaders()
    {
        if (this._requestHeaders === undefined)
            this._requestHeaders = {};
        return this._requestHeaders;
    },

    set requestHeaders(x)
    {
        if (this._requestHeaders === x)
            return;

        this._requestHeaders = x;
        delete this._sortedRequestHeaders;

        this.dispatchEventToListeners("requestHeaders changed");
    },

    get sortedRequestHeaders()
    {
        if (this._sortedRequestHeaders !== undefined)
            return this._sortedRequestHeaders;

        this._sortedRequestHeaders = [];
        for (var key in this.requestHeaders)
            this._sortedRequestHeaders.push({header: key, value: this.requestHeaders[key]});
        this._sortedRequestHeaders.sort(function(a,b) { return a.header.localeCompare(b.header) });

        return this._sortedRequestHeaders;
    },

    get responseHeaders()
    {
        if (this._responseHeaders === undefined)
            this._responseHeaders = {};
        return this._responseHeaders;
    },

    set responseHeaders(x)
    {
        if (this._responseHeaders === x)
            return;

        this._responseHeaders = x;
        delete this._sortedResponseHeaders;

        this.dispatchEventToListeners("responseHeaders changed");
    },

    get sortedResponseHeaders()
    {
        if (this._sortedResponseHeaders !== undefined)
            return this._sortedResponseHeaders;

        this._sortedResponseHeaders = [];
        for (var key in this.responseHeaders)
            this._sortedResponseHeaders.push({header: key, value: this.responseHeaders[key]});
        this._sortedResponseHeaders.sort(function(a,b) { return a.header.localeCompare(b.header) });

        return this._sortedResponseHeaders;
    },

    get scripts()
    {
        if (!("_scripts" in this))
            this._scripts = [];
        return this._scripts;
    },

    addScript: function(script)
    {
        if (!script)
            return;
        this.scripts.unshift(script);
        script.resource = this;
    },

    removeAllScripts: function()
    {
        if (!this._scripts)
            return;

        for (var i = 0; i < this._scripts.length; ++i) {
            if (this._scripts[i].resource === this)
                delete this._scripts[i].resource;
        }

        delete this._scripts;
    },

    removeScript: function(script)
    {
        if (!script)
            return;

        if (script.resource === this)
            delete script.resource;

        if (!this._scripts)
            return;

        this._scripts.remove(script);
    },

    get errors()
    {
        return this._errors || 0;
    },

    set errors(x)
    {
        this._errors = x;
    },

    get warnings()
    {
        return this._warnings || 0;
    },

    set warnings(x)
    {
        this._warnings = x;
    },

    get tips()
    {
        if (!("_tips" in this))
            this._tips = {};
        return this._tips;
    },

    _addTip: function(tip)
    {
        if (tip.id in this.tips)
            return;

        this.tips[tip.id] = tip;

        // FIXME: Re-enable this code once we have a scope bar in the Console.
        // Otherwise, we flood the Console with too many tips.
        /*
        var msg = new WebInspector.ConsoleMessage(WebInspector.ConsoleMessage.MessageSource.Other,
            WebInspector.ConsoleMessage.MessageType.Log, WebInspector.ConsoleMessage.MessageLevel.Tip, 
            -1, this.url, null, 1, tip.message);
        WebInspector.console.addMessage(msg);
        */
    },

    _checkTips: function()
    {
        for (var tip in WebInspector.Tips)
            this._checkTip(WebInspector.Tips[tip]);
    },

    _checkTip: function(tip)
    {
        var addTip = false;
        switch (tip.id) {
            case WebInspector.Tips.ResourceNotCompressed.id:
                addTip = this._shouldCompress();
                break;
        }

        if (addTip)
            this._addTip(tip);
    },

    _shouldCompress: function()
    {
        return WebInspector.Resource.Type.isTextType(this.type)
            && this.domain
            && !("Content-Encoding" in this.responseHeaders)
            && this.contentLength !== undefined
            && this.contentLength >= 512;
    },

    _mimeTypeIsConsistentWithType: function()
    {
        if (typeof this.type === "undefined"
         || this.type === WebInspector.Resource.Type.Other
         || this.type === WebInspector.Resource.Type.XHR)
            return true;

        if (this.mimeType in WebInspector.MIMETypes)
            return this.type in WebInspector.MIMETypes[this.mimeType];

        return true;
    },

    _checkWarnings: function()
    {
        for (var warning in WebInspector.Warnings)
            this._checkWarning(WebInspector.Warnings[warning]);
    },

    _checkWarning: function(warning)
    {
        var msg;
        switch (warning.id) {
            case WebInspector.Warnings.IncorrectMIMEType.id:
                if (!this._mimeTypeIsConsistentWithType())
                    msg = new WebInspector.ConsoleMessage(WebInspector.ConsoleMessage.MessageSource.Other,
                        WebInspector.ConsoleMessage.MessageType.Log, 
                        WebInspector.ConsoleMessage.MessageLevel.Warning, -1, this.url, null, 1,
                        String.sprintf(WebInspector.Warnings.IncorrectMIMEType.message,
                        WebInspector.Resource.Type.toString(this.type), this.mimeType));
                break;
        }

        if (msg)
            WebInspector.console.addMessage(msg);
    }
}

WebInspector.Resource.prototype.__proto__ = WebInspector.Object.prototype;

WebInspector.Resource.CompareByStartTime = function(a, b)
{
    if (a.startTime < b.startTime)
        return -1;
    if (a.startTime > b.startTime)
        return 1;
    return 0;
}

WebInspector.Resource.CompareByResponseReceivedTime = function(a, b)
{
    if (a.responseReceivedTime === -1 && b.responseReceivedTime !== -1)
        return 1;
    if (a.responseReceivedTime !== -1 && b.responseReceivedTime === -1)
        return -1;
    if (a.responseReceivedTime < b.responseReceivedTime)
        return -1;
    if (a.responseReceivedTime > b.responseReceivedTime)
        return 1;
    return 0;
}

WebInspector.Resource.CompareByEndTime = function(a, b)
{
    if (a.endTime === -1 && b.endTime !== -1)
        return 1;
    if (a.endTime !== -1 && b.endTime === -1)
        return -1;
    if (a.endTime < b.endTime)
        return -1;
    if (a.endTime > b.endTime)
        return 1;
    return 0;
}

WebInspector.Resource.CompareByDuration = function(a, b)
{
    if (a.duration < b.duration)
        return -1;
    if (a.duration > b.duration)
        return 1;
    return 0;
}

WebInspector.Resource.CompareByLatency = function(a, b)
{
    if (a.latency < b.latency)
        return -1;
    if (a.latency > b.latency)
        return 1;
    return 0;
}

WebInspector.Resource.CompareBySize = function(a, b)
{
    if (a.contentLength < b.contentLength)
        return -1;
    if (a.contentLength > b.contentLength)
        return 1;
    return 0;
}

WebInspector.Resource.StatusTextForCode = function(code)
{
    return code ? code + " " + WebInspector.Resource.StatusText[code] : "";
}

/* ResourceCategory.js */

/*
 * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer. 
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution. 
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission. 
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.ResourceCategory = function(name, title, color)
{
    WebInspector.AbstractTimelineCategory.call(this, name, title, color);
    this.resources = [];
}

WebInspector.ResourceCategory.prototype = {

    addResource: function(resource)
    {
        var a = resource;
        var resourcesLength = this.resources.length;
        for (var i = 0; i < resourcesLength; ++i) {
            var b = this.resources[i];
            if (a._lastPathComponentLowerCase && b._lastPathComponentLowerCase)
                if (a._lastPathComponentLowerCase < b._lastPathComponentLowerCase)
                    break;
            else if (a.name && b.name)
                if (a.name < b.name)
                    break;
        }

        this.resources.splice(i, 0, resource);
    },

    removeResource: function(resource)
    {
        this.resources.remove(resource, true);
    },

    removeAllResources: function(resource)
    {
        this.resources = [];
    }
}

WebInspector.ResourceCategory.prototype.__proto__ = WebInspector.AbstractTimelineCategory.prototype;

/* Database.js */

/*
 * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.Database = function(id, domain, name, version)
{
    this._id = id;
    this._domain = domain;
    this._name = name;
    this._version = version;
}

WebInspector.Database.prototype = {
    get id()
    {
        return this._id;
    },

    get name()
    {
        return this._name;
    },

    set name(x)
    {
        this._name = x;
    },

    get version()
    {
        return this._version;
    },

    set version(x)
    {
        this._version = x;
    },

    get domain()
    {
        return this._domain;
    },

    set domain(x)
    {
        this._domain = x;
    },

    get displayDomain()
    {
        return WebInspector.Resource.prototype.__lookupGetter__("displayDomain").call(this);
    },

    getTableNames: function(callback)
    {
        function sortingCallback(names)
        {
            callback(names.sort());
        }
        var callId = WebInspector.Callback.wrap(sortingCallback);
        InspectorController.getDatabaseTableNames(callId, this._id);
    },
    
    executeSql: function(query, onSuccess, onError)
    {
        function callback(result)
        {
            if (!(result instanceof Array)) {
                onError(result);
                return;
            }
            onSuccess(result);
        }
        InjectedScriptAccess.executeSql(this._id, query, callback);
    }
}

WebInspector.didGetDatabaseTableNames = WebInspector.Callback.processCallback;

/* DOMStorage.js */

/*
 * Copyright (C) 2008 Nokia Inc.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.DOMStorage = function(id, domain, isLocalStorage)
{
    this._id = id;
    this._domain = domain;
    this._isLocalStorage = isLocalStorage;
}

WebInspector.DOMStorage.prototype = {
    get id()
    {
        return this._id;
    },

    get domStorage()
    {
        return this._domStorage;
    },

    get domain()
    {
        return this._domain;
    },

    get isLocalStorage()
    {
        return this._isLocalStorage;
    },

    getEntries: function(callback)
    {
        var callId = WebInspector.Callback.wrap(callback);
        InspectorController.getDOMStorageEntries(callId, this._id);
    },
    
    setItem: function(key, value, callback)
    {
        var callId = WebInspector.Callback.wrap(callback);
        InspectorController.setDOMStorageItem(callId, this._id, key, value);
    },
    
    removeItem: function(key, callback)
    {
        var callId = WebInspector.Callback.wrap(callback);
        InspectorController.removeDOMStorageItem(callId, this._id, key);
    }
}

WebInspector.didGetDOMStorageEntries = WebInspector.Callback.processCallback;
WebInspector.didSetDOMStorageItem = WebInspector.Callback.processCallback;
WebInspector.didRemoveDOMStorageItem = WebInspector.Callback.processCallback;

/* DOMStorageItemsView.js */

/*
 * Copyright (C) 2008 Nokia Inc.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.DOMStorageItemsView = function(domStorage)
{
    WebInspector.View.call(this);

    this.domStorage = domStorage;

    this.element.addStyleClass("storage-view");
    this.element.addStyleClass("table");

    this.deleteButton = new WebInspector.StatusBarButton(WebInspector.UIString("Delete"), "delete-storage-status-bar-item");
    this.deleteButton.visible = false;
    this.deleteButton.addEventListener("click", this._deleteButtonClicked.bind(this), false);

    this.refreshButton = new WebInspector.StatusBarButton(WebInspector.UIString("Refresh"), "refresh-storage-status-bar-item");
    this.refreshButton.addEventListener("click", this._refreshButtonClicked.bind(this), false);
}

WebInspector.DOMStorageItemsView.prototype = {
    get statusBarItems()
    {
        return [this.refreshButton.element, this.deleteButton.element];
    },

    show: function(parentElement)
    {
        WebInspector.View.prototype.show.call(this, parentElement);
        this.update();
    },

    hide: function()
    {
        WebInspector.View.prototype.hide.call(this);
        this.deleteButton.visible = false;
    },

    update: function()
    {
        this.element.removeChildren();
        var callback = this._showDOMStorageEntries.bind(this);
        this.domStorage.getEntries(callback);
    },

    _showDOMStorageEntries: function(entries)
    {
        this._dataGrid = this._dataGridForDOMStorageEntries(entries);
        this.element.appendChild(this._dataGrid.element);
        this._dataGrid.updateWidths();
        this.deleteButton.visible = true;
    },

    resize: function()
    {
        if (this._dataGrid)
            this._dataGrid.updateWidths();
    },

    _dataGridForDOMStorageEntries: function(entries)
    {
        var columns = {};
        columns[0] = {};
        columns[1] = {};
        columns[0].title = WebInspector.UIString("Key");
        columns[0].width = columns[0].title.length;
        columns[1].title = WebInspector.UIString("Value");
        columns[1].width = columns[1].title.length;

        var nodes = [];

        var keys = [];
        var length = entries.length;
        for (var i = 0; i < entries.length; i++) {
            var data = {};

            var key = entries[i][0];
            data[0] = key;
            if (key.length > columns[0].width)
                columns[0].width = key.length;

            var value = entries[i][1];
            data[1] = value;
            if (value.length > columns[1].width)
                columns[1].width = value.length;
            var node = new WebInspector.DataGridNode(data, false);
            node.selectable = true;
            nodes.push(node);
            keys.push(key);
        }

        var totalColumnWidths = columns[0].width + columns[1].width;
        var width = Math.round((columns[0].width * 100) / totalColumnWidths);
        const minimumPrecent = 10;
        if (width < minimumPrecent)
            width = minimumPrecent;
        if (width > 100 - minimumPrecent)
            width = 100 - minimumPrecent;
        columns[0].width = width;
        columns[1].width = 100 - width;
        columns[0].width += "%";
        columns[1].width += "%";

        var dataGrid = new WebInspector.DataGrid(columns, this._editingCallback.bind(this), this._deleteCallback.bind(this));
        var length = nodes.length;
        for (var i = 0; i < length; ++i)
            dataGrid.appendChild(nodes[i]);
        dataGrid.addCreationNode(false);
        if (length > 0)
            nodes[0].selected = true;
        return dataGrid;
    },

    _deleteButtonClicked: function(event)
    {
        if (!this._dataGrid || !this._dataGrid.selectedNode)
            return;

        this._deleteCallback(this._dataGrid.selectedNode);
    },

    _refreshButtonClicked: function(event)
    {
        this.update();
    },
    
    _editingCallback: function(editingNode, columnIdentifier, oldText, newText)
    {
        var domStorage = this.domStorage;
        if (columnIdentifier === 0) {
            if (oldText)
                domStorage.removeItem(oldText);

            domStorage.setItem(newText, editingNode.data[1]);
        } else {
            domStorage.setItem(editingNode.data[0], newText);
        }
        
        this.update();
    },
    
    _deleteCallback: function(node)
    {
        if (!node || node.isCreationNode)
            return;

        if (this.domStorage)
            this.domStorage.removeItem(node.data[0]);
            
        this.update();
    }
}

WebInspector.DOMStorageItemsView.prototype.__proto__ = WebInspector.View.prototype;

/* DataGrid.js */

/*
 * Copyright (C) 2008 Apple Inc. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *        notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *        notice, this list of conditions and the following disclaimer in the
 *        documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.         IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.DataGrid = function(columns, editCallback, deleteCallback)
{
    this.element = document.createElement("div");
    this.element.className = "data-grid";
    this.element.tabIndex = 0;
    this.element.addEventListener("keydown", this._keyDown.bind(this), false);

    this._headerTable = document.createElement("table");
    this._headerTable.className = "header";

    this._dataTable = document.createElement("table");
    this._dataTable.className = "data";

    this._dataTable.addEventListener("mousedown", this._mouseDownInDataTable.bind(this), true);
    this._dataTable.addEventListener("click", this._clickInDataTable.bind(this), true);
    
    // FIXME: Add a createCallback which is different from editCallback and has different
    // behavior when creating a new node.
    if (editCallback) {
        this._dataTable.addEventListener("dblclick", this._ondblclick.bind(this), false);
        this._editCallback = editCallback;
    }
    if (deleteCallback)
        this._deleteCallback = deleteCallback;
    
    this.aligned = {};

    var scrollContainer = document.createElement("div");
    scrollContainer.className = "data-container";
    scrollContainer.appendChild(this._dataTable);

    this.element.appendChild(this._headerTable);
    this.element.appendChild(scrollContainer);

    var headerRow = document.createElement("tr");
    var columnGroup = document.createElement("colgroup");
    var columnCount = 0;

    for (var columnIdentifier in columns) {
        var column = columns[columnIdentifier];
        if (column.disclosure)
            this.disclosureColumnIdentifier = columnIdentifier;

        var col = document.createElement("col");
        if (column.width)
            col.style.width = column.width;
        columnGroup.appendChild(col);

        var cell = document.createElement("th");
        cell.className = columnIdentifier + "-column";
        cell.columnIdentifier = columnIdentifier;

        var div = document.createElement("div");
        div.textContent = column.title;
        cell.appendChild(div);

        if (column.sort) {
            cell.addStyleClass("sort-" + column.sort);
            this._sortColumnCell = cell;
        }

        if (column.sortable) {
            cell.addEventListener("click", this._clickInHeaderCell.bind(this), false);
            cell.addStyleClass("sortable");
        }

        if (column.aligned) {
            cell.addStyleClass(column.aligned);
            this.aligned[columnIdentifier] = column.aligned;
        }

        headerRow.appendChild(cell);

        ++columnCount;
    }

    columnGroup.span = columnCount;

    var cell = document.createElement("th");
    cell.className = "corner";
    headerRow.appendChild(cell);

    this._headerTableColumnGroup = columnGroup;
    this._headerTable.appendChild(this._headerTableColumnGroup);
    this.headerTableBody.appendChild(headerRow);

    var fillerRow = document.createElement("tr");
    fillerRow.className = "filler";

    for (var i = 0; i < columnCount; ++i) {
        var cell = document.createElement("td");
        fillerRow.appendChild(cell);
    }
    
    this._dataTableColumnGroup = columnGroup.cloneNode(true);
    this._dataTable.appendChild(this._dataTableColumnGroup);
    this.dataTableBody.appendChild(fillerRow);

    this.columns = columns || {};
    this.children = [];
    this.selectedNode = null;
    this.expandNodesWhenArrowing = false;
    this.root = true;
    this.hasChildren = false;
    this.expanded = true;
    this.revealed = true;
    this.selected = false;
    this.dataGrid = this;
    this.indentWidth = 15;
    this.resizers = [];
    this.columnWidthsInitialized = false;
}

WebInspector.DataGrid.prototype = {
    _ondblclick: function(event)
    {
        if (this._editing || this._editingNode)
            return;

        this._startEditing(event);
    },

    _startEditingColumnOfDataGridNode: function(node, column)
    {
        this._editing = true;
        this._editingNode = node;
        this._editingNode.select();

        var element = this._editingNode._element.children[column];
        WebInspector.startEditing(element, this._editingCommitted.bind(this), this._editingCancelled.bind(this), element.textContent);
        window.getSelection().setBaseAndExtent(element, 0, element, 1);
    },

    _startEditing: function(event)
    {
        var element = event.target.enclosingNodeOrSelfWithNodeName("td");
        if (!element)
            return;

        this._editingNode = this.dataGridNodeFromEvent(event);
        if (!this._editingNode) {
            if (!this.creationNode)
                return;
            this._editingNode = this.creationNode;
        }

        // Force editing the 1st column when editing the creation node
        if (this._editingNode.isCreationNode)
            return this._startEditingColumnOfDataGridNode(this._editingNode, 0);

        this._editing = true;
        WebInspector.startEditing(element, this._editingCommitted.bind(this), this._editingCancelled.bind(this), element.textContent);
        window.getSelection().setBaseAndExtent(element, 0, element, 1);
    },

    _editingCommitted: function(element, newText, oldText, context, moveDirection)
    {
        // FIXME: We need more column identifiers here throughout this function.
        // Not needed yet since only editable DataGrid is DOM Storage, which is Key - Value.
        
        // FIXME: Better way to do this than regular expressions?
        var columnIdentifier = parseInt(element.className.match(/\b(\d+)-column\b/)[1]);

        var textBeforeEditing = this._editingNode.data[columnIdentifier];
        var currentEditingNode = this._editingNode;

        function moveToNextIfNeeded(wasChange) {
            if (!moveDirection)
                return;

            if (moveDirection === "forward") {
                if (currentEditingNode.isCreationNode && columnIdentifier === 0 && !wasChange)
                    return;

                if (columnIdentifier === 0)
                    return this._startEditingColumnOfDataGridNode(currentEditingNode, 1);

                var nextDataGridNode = currentEditingNode.traverseNextNode(true, null, true);
                if (nextDataGridNode)
                    return this._startEditingColumnOfDataGridNode(nextDataGridNode, 0);
                if (currentEditingNode.isCreationNode && wasChange) {
                    addCreationNode(false);
                    return this._startEditingColumnOfDataGridNode(this.creationNode, 0);
                }
                return;
            }

            if (moveDirection === "backward") {
                if (columnIdentifier === 1)
                    return this._startEditingColumnOfDataGridNode(currentEditingNode, 0);
                    var nextDataGridNode = currentEditingNode.traversePreviousNode(true, null, true);

                if (nextDataGridNode)
                    return this._startEditingColumnOfDataGridNode(nextDataGridNode, 1);
                return;
            }
        }

        if (textBeforeEditing == newText) {
            this._editingCancelled(element);
            moveToNextIfNeeded.call(this, false);
            return;
        }

        // Update the text in the datagrid that we typed
        this._editingNode.data[columnIdentifier] = newText;
        
        // Make the callback - expects an editing node (table row), the column number that is being edited,
        // the text that used to be there, and the new text.
        this._editCallback(this._editingNode, columnIdentifier, textBeforeEditing, newText);

        if (this._editingNode.isCreationNode)
            this.addCreationNode(false);

        this._editingCancelled(element);
        moveToNextIfNeeded.call(this, true);
    },

    _editingCancelled: function(element, context)
    {
        delete this._editing;
        this._editingNode = null;
    },
    
    get sortColumnIdentifier()
    {
        if (!this._sortColumnCell)
            return null;
        return this._sortColumnCell.columnIdentifier;
    },

    get sortOrder()
    {
        if (!this._sortColumnCell || this._sortColumnCell.hasStyleClass("sort-ascending"))
            return "ascending";
        if (this._sortColumnCell.hasStyleClass("sort-descending"))
            return "descending";
        return null;
    },

    get headerTableBody()
    {
        if ("_headerTableBody" in this)
            return this._headerTableBody;

        this._headerTableBody = this._headerTable.getElementsByTagName("tbody")[0];
        if (!this._headerTableBody) {
            this._headerTableBody = this.element.ownerDocument.createElement("tbody");
            this._headerTable.insertBefore(this._headerTableBody, this._headerTable.tFoot);
        }

        return this._headerTableBody;
    },

    get dataTableBody()
    {
        if ("_dataTableBody" in this)
            return this._dataTableBody;

        this._dataTableBody = this._dataTable.getElementsByTagName("tbody")[0];
        if (!this._dataTableBody) {
            this._dataTableBody = this.element.ownerDocument.createElement("tbody");
            this._dataTable.insertBefore(this._dataTableBody, this._dataTable.tFoot);
        }

        return this._dataTableBody;
    },
 
    // Updates the widths of the table, including the positions of the column
    // resizers.
    //
    // IMPORTANT: This function MUST be called once after the element of the
    // DataGrid is attached to its parent element and every subsequent time the
    // width of the parent element is changed in order to make it possible to
    // resize the columns.
    //
    // If this function is not called after the DataGrid is attached to its
    // parent element, then the DataGrid's columns will not be resizable.
    updateWidths: function()
    {
        var headerTableColumns = this._headerTableColumnGroup.children;
        
        var left = 0;
        var tableWidth = this._dataTable.offsetWidth;
        var numColumns = headerTableColumns.length;
        
        if (!this.columnWidthsInitialized) {
            // Give all the columns initial widths now so that during a resize,
            // when the two columns that get resized get a percent value for
            // their widths, all the other columns already have percent values
            // for their widths.
            for (var i = 0; i < numColumns; i++) {
                var columnWidth = this.headerTableBody.rows[0].cells[i].offsetWidth;
                var percentWidth = ((columnWidth / tableWidth) * 100) + "%";
                this._headerTableColumnGroup.children[i].style.width = percentWidth;
                this._dataTableColumnGroup.children[i].style.width = percentWidth;
            }
            this.columnWidthsInitialized = true;
        }
        
        // Make n - 1 resizers for n columns. 
        for (var i = 0; i < numColumns - 1; i++) {
            var resizer = this.resizers[i];

            if (!resizer) {
                // This is the first call to updateWidth, so the resizers need
                // to be created.
                resizer = document.createElement("div");
                resizer.addStyleClass("data-grid-resizer");
                // This resizer is associated with the column to its right.
                resizer.rightNeighboringColumnID = i + 1;
                resizer.addEventListener("mousedown", this._startResizerDragging.bind(this), false);
                this.element.appendChild(resizer);
                this.resizers[i] = resizer;
            }

            // Get the width of the cell in the first (and only) row of the
            // header table in order to determine the width of the column, since
            // it is not possible to query a column for its width.
            left += this.headerTableBody.rows[0].cells[i].offsetWidth;
            
            resizer.style.left = left + "px";
        }
    },

    addCreationNode: function(hasChildren)
    {
        if (this.creationNode)
            this.creationNode.makeNormal();

        var emptyData = {};
        for (var column in this.columns)
            emptyData[column] = '';
        this.creationNode = new WebInspector.CreationDataGridNode(emptyData, hasChildren);
        this.appendChild(this.creationNode);
    },

    appendChild: function(child)
    {
        this.insertChild(child, this.children.length);
    },

    insertChild: function(child, index)
    {
        if (!child)
            throw("insertChild: Node can't be undefined or null.");
        if (child.parent === this)
            throw("insertChild: Node is already a child of this node.");

        if (child.parent)
            child.parent.removeChild(child);

        this.children.splice(index, 0, child);
        this.hasChildren = true;

        child.parent = this;
        child.dataGrid = this.dataGrid;
        child._recalculateSiblings(index);

        delete child._depth;
        delete child._revealed;
        delete child._attached;

        var current = child.children[0];
        while (current) {
            current.dataGrid = this.dataGrid;
            delete current._depth;
            delete current._revealed;
            delete current._attached;
            current = current.traverseNextNode(false, child, true);
        }

        if (this.expanded)
            child._attach();
    },

    removeChild: function(child)
    {
        if (!child)
            throw("removeChild: Node can't be undefined or null.");
        if (child.parent !== this)
            throw("removeChild: Node is not a child of this node.");

        child.deselect();

        this.children.remove(child, true);

        if (child.previousSibling)
            child.previousSibling.nextSibling = child.nextSibling;
        if (child.nextSibling)
            child.nextSibling.previousSibling = child.previousSibling;

        child.dataGrid = null;
        child.parent = null;
        child.nextSibling = null;
        child.previousSibling = null;

        if (this.children.length <= 0)
            this.hasChildren = false;
    },

    removeChildren: function()
    {
        for (var i = 0; i < this.children.length; ++i) {
            var child = this.children[i];
            child.deselect();
            child._detach();

            child.dataGrid = null;
            child.parent = null;
            child.nextSibling = null;
            child.previousSibling = null;
        }

        this.children = [];
        this.hasChildren = false;
    },

    removeChildrenRecursive: function()
    {
        var childrenToRemove = this.children;

        var child = this.children[0];
        while (child) {
            if (child.children.length)
                childrenToRemove = childrenToRemove.concat(child.children);
            child = child.traverseNextNode(false, this, true);
        }

        for (var i = 0; i < childrenToRemove.length; ++i) {
            var child = childrenToRemove[i];
            child.deselect();
            child._detach();

            child.children = [];
            child.dataGrid = null;
            child.parent = null;
            child.nextSibling = null;
            child.previousSibling = null;
        }

        this.children = [];
    },

    handleKeyEvent: function(event)
    {
        if (!this.selectedNode || event.shiftKey || event.metaKey || event.ctrlKey || this._editing)
            return false;

        var handled = false;
        var nextSelectedNode;
        if (event.keyIdentifier === "Up" && !event.altKey) {
            nextSelectedNode = this.selectedNode.traversePreviousNode(true);
            while (nextSelectedNode && !nextSelectedNode.selectable)
                nextSelectedNode = nextSelectedNode.traversePreviousNode(!this.expandTreeNodesWhenArrowing);
            handled = nextSelectedNode ? true : false;
        } else if (event.keyIdentifier === "Down" && !event.altKey) {
            nextSelectedNode = this.selectedNode.traverseNextNode(true);
            while (nextSelectedNode && !nextSelectedNode.selectable)
                nextSelectedNode = nextSelectedNode.traverseNextNode(!this.expandTreeNodesWhenArrowing);
            handled = nextSelectedNode ? true : false;
        } else if (event.keyIdentifier === "Left") {
            if (this.selectedNode.expanded) {
                if (event.altKey)
                    this.selectedNode.collapseRecursively();
                else
                    this.selectedNode.collapse();
                handled = true;
            } else if (this.selectedNode.parent && !this.selectedNode.parent.root) {
                handled = true;
                if (this.selectedNode.parent.selectable) {
                    nextSelectedNode = this.selectedNode.parent;
                    handled = nextSelectedNode ? true : false;
                } else if (this.selectedNode.parent)
                    this.selectedNode.parent.collapse();
            }
        } else if (event.keyIdentifier === "Right") {
            if (!this.selectedNode.revealed) {
                this.selectedNode.reveal();
                handled = true;
            } else if (this.selectedNode.hasChildren) {
                handled = true;
                if (this.selectedNode.expanded) {
                    nextSelectedNode = this.selectedNode.children[0];
                    handled = nextSelectedNode ? true : false;
                } else {
                    if (event.altKey)
                        this.selectedNode.expandRecursively();
                    else
                        this.selectedNode.expand();
                }
            }
        } else if (event.keyCode === 8 || event.keyCode === 46) {
            if (this._deleteCallback) {
                handled = true;
                this._deleteCallback(this.selectedNode);
            }
        }

        if (nextSelectedNode) {
            nextSelectedNode.reveal();
            nextSelectedNode.select();
        }

        if (handled) {
            event.preventDefault();
            event.stopPropagation();
        }

        return handled;
    },

    expand: function()
    {
        // This is the root, do nothing.
    },

    collapse: function()
    {
        // This is the root, do nothing.
    },

    reveal: function()
    {
        // This is the root, do nothing.
    },

    dataGridNodeFromEvent: function(event)
    {
        var rowElement = event.target.enclosingNodeOrSelfWithNodeName("tr");
        return rowElement._dataGridNode;
    },

    dataGridNodeFromPoint: function(x, y)
    {
        var node = this._dataTable.ownerDocument.elementFromPoint(x, y);
        var rowElement = node.enclosingNodeOrSelfWithNodeName("tr");
        return rowElement._dataGridNode;
    },

    _keyDown: function(event)
    {
        this.handleKeyEvent(event);
    },

    _clickInHeaderCell: function(event)
    {
        var cell = event.target.enclosingNodeOrSelfWithNodeName("th");
        if (!cell || !cell.columnIdentifier || !cell.hasStyleClass("sortable"))
            return;

        var sortOrder = this.sortOrder;

        if (this._sortColumnCell) {
            this._sortColumnCell.removeStyleClass("sort-ascending");
            this._sortColumnCell.removeStyleClass("sort-descending");
        }

        if (cell == this._sortColumnCell) {
            if (sortOrder == "ascending")
                sortOrder = "descending";
            else
                sortOrder = "ascending";
        }

        this._sortColumnCell = cell;

        cell.addStyleClass("sort-" + sortOrder);

        this.dispatchEventToListeners("sorting changed");
    },

    _mouseDownInDataTable: function(event)
    {
        var gridNode = this.dataGridNodeFromEvent(event);
        if (!gridNode || !gridNode.selectable)
            return;

        if (gridNode.isEventWithinDisclosureTriangle(event))
            return;

        if (event.metaKey) {
            if (gridNode.selected)
                gridNode.deselect();
            else
                gridNode.select();
        } else
            gridNode.select();
    },

    _clickInDataTable: function(event)
    {
        var gridNode = this.dataGridNodeFromEvent(event);
        if (!gridNode || !gridNode.hasChildren)
            return;

        if (!gridNode.isEventWithinDisclosureTriangle(event))
            return;

        if (gridNode.expanded) {
            if (event.altKey)
                gridNode.collapseRecursively();
            else
                gridNode.collapse();
        } else {
            if (event.altKey)
                gridNode.expandRecursively();
            else
                gridNode.expand();
        }
    },
    
    _startResizerDragging: function(event)
    {
        this.currentResizer = event.target;
        if (!this.currentResizer.rightNeighboringColumnID)
            return;
        WebInspector.elementDragStart(this.lastResizer, this._resizerDragging.bind(this),
            this._endResizerDragging.bind(this), event, "col-resize");
    },
    
    _resizerDragging: function(event)
    {
        var resizer = this.currentResizer;
        if (!resizer)
            return;
        
        // Constrain the dragpoint to be within the containing div of the
        // datagrid.
        var dragPoint = event.clientX - this.element.totalOffsetLeft;
        // Constrain the dragpoint to be within the space made up by the
        // column directly to the left and the column directly to the right.
        var leftEdgeOfPreviousColumn = 0;
        var firstRowCells = this.headerTableBody.rows[0].cells;
        for (var i = 0; i < resizer.rightNeighboringColumnID - 1; i++)
            leftEdgeOfPreviousColumn += firstRowCells[i].offsetWidth;
            
        var rightEdgeOfNextColumn = leftEdgeOfPreviousColumn + firstRowCells[resizer.rightNeighboringColumnID - 1].offsetWidth + firstRowCells[resizer.rightNeighboringColumnID].offsetWidth;
        
        // Give each column some padding so that they don't disappear.               
        var leftMinimum = leftEdgeOfPreviousColumn + this.ColumnResizePadding;
        var rightMaximum = rightEdgeOfNextColumn - this.ColumnResizePadding;
        
        dragPoint = Number.constrain(dragPoint, leftMinimum, rightMaximum);
        
        resizer.style.left = (dragPoint - this.CenterResizerOverBorderAdjustment) + "px";
        
        var percentLeftColumn = (((dragPoint - leftEdgeOfPreviousColumn) / this._dataTable.offsetWidth) * 100) + "%";
        this._headerTableColumnGroup.children[resizer.rightNeighboringColumnID - 1].style.width = percentLeftColumn;
        this._dataTableColumnGroup.children[resizer.rightNeighboringColumnID - 1].style.width = percentLeftColumn;
        
        var percentRightColumn = (((rightEdgeOfNextColumn - dragPoint) / this._dataTable.offsetWidth) * 100) + "%";
        this._headerTableColumnGroup.children[resizer.rightNeighboringColumnID].style.width =  percentRightColumn;
        this._dataTableColumnGroup.children[resizer.rightNeighboringColumnID].style.width = percentRightColumn;
        
        event.preventDefault();
    },
    
    _endResizerDragging: function(event)
    {
        WebInspector.elementDragEnd(event);
        this.currentResizer = null;
    },
    
    ColumnResizePadding: 10,
    
    CenterResizerOverBorderAdjustment: 3,
}

WebInspector.DataGrid.prototype.__proto__ = WebInspector.Object.prototype;

WebInspector.DataGridNode = function(data, hasChildren)
{
    this._expanded = false;
    this._selected = false;
    this._shouldRefreshChildren = true;
    this._data = data || {};
    this.hasChildren = hasChildren || false;
    this.children = [];
    this.dataGrid = null;
    this.parent = null;
    this.previousSibling = null;
    this.nextSibling = null;
    this.disclosureToggleWidth = 10;
}

WebInspector.DataGridNode.prototype = {
    selectable: true,

    get element()
    {
        if (this._element)
            return this._element;

        if (!this.dataGrid)
            return null;

        this._element = document.createElement("tr");
        this._element._dataGridNode = this;

        if (this.hasChildren)
            this._element.addStyleClass("parent");
        if (this.expanded)
            this._element.addStyleClass("expanded");
        if (this.selected)
            this._element.addStyleClass("selected");
        if (this.revealed)
            this._element.addStyleClass("revealed");

        for (var columnIdentifier in this.dataGrid.columns) {
            var cell = this.createCell(columnIdentifier);
            this._element.appendChild(cell);
        }

        return this._element;
    },

    get data()
    {
        return this._data;
    },

    set data(x)
    {
        this._data = x || {};
        this.refresh();
    },

    get revealed()
    {
        if ("_revealed" in this)
            return this._revealed;

        var currentAncestor = this.parent;
        while (currentAncestor && !currentAncestor.root) {
            if (!currentAncestor.expanded) {
                this._revealed = false;
                return false;
            }

            currentAncestor = currentAncestor.parent;
        }

        this._revealed = true;
        return true;
    },

    set hasChildren(x)
    {
        if (this._hasChildren === x)
            return;

        this._hasChildren = x;

        if (!this._element)
            return;

        if (this._hasChildren)
        {
            this._element.addStyleClass("parent");
            if (this.expanded)
                this._element.addStyleClass("expanded");
        }
        else
        {
            this._element.removeStyleClass("parent");
            this._element.removeStyleClass("expanded");
        }
    },

    get hasChildren()
    {
        return this._hasChildren;
    },

    set revealed(x)
    {
        if (this._revealed === x)
            return;

        this._revealed = x;

        if (this._element) {
            if (this._revealed)
                this._element.addStyleClass("revealed");
            else
                this._element.removeStyleClass("revealed");
        }

        for (var i = 0; i < this.children.length; ++i)
            this.children[i].revealed = x && this.expanded;
    },

    get depth()
    {
        if ("_depth" in this)
            return this._depth;
        if (this.parent && !this.parent.root)
            this._depth = this.parent.depth + 1;
        else
            this._depth = 0;
        return this._depth;
    },

    get shouldRefreshChildren()
    {
        return this._shouldRefreshChildren;
    },

    set shouldRefreshChildren(x)
    {
        this._shouldRefreshChildren = x;
        if (x && this.expanded)
            this.expand();
    },

    get selected()
    {
        return this._selected;
    },

    set selected(x)
    {
        if (x)
            this.select();
        else
            this.deselect();
    },

    get expanded()
    {
        return this._expanded;
    },

    set expanded(x)
    {
        if (x)
            this.expand();
        else
            this.collapse();
    },

    refresh: function()
    {
        if (!this._element || !this.dataGrid)
            return;

        this._element.removeChildren();

        for (var columnIdentifier in this.dataGrid.columns) {
            var cell = this.createCell(columnIdentifier);
            this._element.appendChild(cell);
        }
    },

    createCell: function(columnIdentifier)
    {
        var cell = document.createElement("td");
        cell.className = columnIdentifier + "-column";

        var alignment = this.dataGrid.aligned[columnIdentifier];
        if (alignment)
            cell.addStyleClass(alignment);

        var div = document.createElement("div");
        div.textContent = this.data[columnIdentifier];
        cell.appendChild(div);

        if (columnIdentifier === this.dataGrid.disclosureColumnIdentifier) {
            cell.addStyleClass("disclosure");
            if (this.depth)
                cell.style.setProperty("padding-left", (this.depth * this.dataGrid.indentWidth) + "px");
        }

        return cell;
    },

    // Share these functions with DataGrid. They are written to work with a DataGridNode this object.
    appendChild: WebInspector.DataGrid.prototype.appendChild,
    insertChild: WebInspector.DataGrid.prototype.insertChild,
    removeChild: WebInspector.DataGrid.prototype.removeChild,
    removeChildren: WebInspector.DataGrid.prototype.removeChildren,
    removeChildrenRecursive: WebInspector.DataGrid.prototype.removeChildrenRecursive,

    _recalculateSiblings: function(myIndex)
    {
        if (!this.parent)
            return;

        var previousChild = (myIndex > 0 ? this.parent.children[myIndex - 1] : null);

        if (previousChild) {
            previousChild.nextSibling = this;
            this.previousSibling = previousChild;
        } else
            this.previousSibling = null;

        var nextChild = this.parent.children[myIndex + 1];

        if (nextChild) {
            nextChild.previousSibling = this;
            this.nextSibling = nextChild;
        } else
            this.nextSibling = null;
    },

    collapse: function()
    {
        if (this._element)
            this._element.removeStyleClass("expanded");

        this._expanded = false;

        for (var i = 0; i < this.children.length; ++i)
            this.children[i].revealed = false;

        this.dispatchEventToListeners("collapsed");
    },

    collapseRecursively: function()
    {
        var item = this;
        while (item) {
            if (item.expanded)
                item.collapse();
            item = item.traverseNextNode(false, this, true);
        }
    },

    expand: function()
    {
        if (!this.hasChildren || this.expanded)
            return;

        if (this.revealed && !this._shouldRefreshChildren)
            for (var i = 0; i < this.children.length; ++i)
                this.children[i].revealed = true;

        if (this._shouldRefreshChildren) {
            for (var i = 0; i < this.children.length; ++i)
                this.children[i]._detach();

            this.dispatchEventToListeners("populate");

            if (this._attached) {
                for (var i = 0; i < this.children.length; ++i) {
                    var child = this.children[i];
                    if (this.revealed)
                        child.revealed = true;
                    child._attach();
                }
            }

            delete this._shouldRefreshChildren;
        }

        if (this._element)
            this._element.addStyleClass("expanded");

        this._expanded = true;

        this.dispatchEventToListeners("expanded");
    },

    expandRecursively: function()
    {
        var item = this;
        while (item) {
            item.expand();
            item = item.traverseNextNode(false, this);
        }
    },

    reveal: function()
    {
        var currentAncestor = this.parent;
        while (currentAncestor && !currentAncestor.root) {
            if (!currentAncestor.expanded)
                currentAncestor.expand();
            currentAncestor = currentAncestor.parent;
        }

        this.element.scrollIntoViewIfNeeded(false);

        this.dispatchEventToListeners("revealed");
    },

    select: function(supressSelectedEvent)
    {
        if (!this.dataGrid || !this.selectable || this.selected)
            return;

        if (this.dataGrid.selectedNode)
            this.dataGrid.selectedNode.deselect();

        this._selected = true;
        this.dataGrid.selectedNode = this;

        if (this._element)
            this._element.addStyleClass("selected");

        if (!supressSelectedEvent)
            this.dispatchEventToListeners("selected");
    },

    deselect: function(supressDeselectedEvent)
    {
        if (!this.dataGrid || this.dataGrid.selectedNode !== this || !this.selected)
            return;

        this._selected = false;
        this.dataGrid.selectedNode = null;

        if (this._element)
            this._element.removeStyleClass("selected");

        if (!supressDeselectedEvent)
            this.dispatchEventToListeners("deselected");
    },

    traverseNextNode: function(skipHidden, stayWithin, dontPopulate, info)
    {
        if (!dontPopulate && this.hasChildren)
            this.dispatchEventToListeners("populate");

        if (info)
            info.depthChange = 0;

        var node = (!skipHidden || this.revealed) ? this.children[0] : null;
        if (node && (!skipHidden || this.expanded)) {
            if (info)
                info.depthChange = 1;
            return node;
        }

        if (this === stayWithin)
            return null;

        node = (!skipHidden || this.revealed) ? this.nextSibling : null;
        if (node)
            return node;

        node = this;
        while (node && !node.root && !((!skipHidden || node.revealed) ? node.nextSibling : null) && node.parent !== stayWithin) {
            if (info)
                info.depthChange -= 1;
            node = node.parent;
        }

        if (!node)
            return null;

        return (!skipHidden || node.revealed) ? node.nextSibling : null;
    },

    traversePreviousNode: function(skipHidden, dontPopulate)
    {
        var node = (!skipHidden || this.revealed) ? this.previousSibling : null;
        if (!dontPopulate && node && node.hasChildren)
            node.dispatchEventToListeners("populate");

        while (node && ((!skipHidden || (node.revealed && node.expanded)) ? node.children[node.children.length - 1] : null)) {
            if (!dontPopulate && node.hasChildren)
                node.dispatchEventToListeners("populate");
            node = ((!skipHidden || (node.revealed && node.expanded)) ? node.children[node.children.length - 1] : null);
        }

        if (node)
            return node;

        if (!this.parent || this.parent.root)
            return null;

        return this.parent;
    },

    isEventWithinDisclosureTriangle: function(event)
    {
        if (!this.hasChildren)
            return false;
        var cell = event.target.enclosingNodeOrSelfWithNodeName("td");
        if (!cell.hasStyleClass("disclosure"))
            return false;
        var computedLeftPadding = window.getComputedStyle(cell).getPropertyCSSValue("padding-left").getFloatValue(CSSPrimitiveValue.CSS_PX);
        var left = cell.totalOffsetLeft + computedLeftPadding;
        return event.pageX >= left && event.pageX <= left + this.disclosureToggleWidth;
    },

    _attach: function()
    {
        if (!this.dataGrid || this._attached)
            return;

        this._attached = true;

        var nextNode = null;
        var previousNode = this.traversePreviousNode(true, true);
        if (previousNode && previousNode.element.parentNode && previousNode.element.nextSibling)
            var nextNode = previousNode.element.nextSibling;
        if (!nextNode)
            nextNode = this.dataGrid.dataTableBody.lastChild;
        this.dataGrid.dataTableBody.insertBefore(this.element, nextNode);

        if (this.expanded)
            for (var i = 0; i < this.children.length; ++i)
                this.children[i]._attach();
    },

    _detach: function()
    {
        if (!this._attached)
            return;

        this._attached = false;

        if (this._element && this._element.parentNode)
            this._element.parentNode.removeChild(this._element);

        for (var i = 0; i < this.children.length; ++i)
            this.children[i]._detach();
    }
}

WebInspector.DataGridNode.prototype.__proto__ = WebInspector.Object.prototype;

WebInspector.CreationDataGridNode = function(data, hasChildren)
{
    WebInspector.DataGridNode.call(this, data, hasChildren);
    this.isCreationNode = true;
}

WebInspector.CreationDataGridNode.prototype = {
    makeNormal: function()
    {
        delete this.isCreationNode;
        delete this.makeNormal;
    }
}

WebInspector.CreationDataGridNode.prototype.__proto__ = WebInspector.DataGridNode.prototype;

/* CookieItemsView.js */

/*
 * Copyright (C) 2009 Apple Inc.  All rights reserved.
 * Copyright (C) 2009 Joseph Pecoraro
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.CookieItemsView = function(cookieDomain)
{
    WebInspector.View.call(this);

    this.element.addStyleClass("storage-view");
    this.element.addStyleClass("table");

    this.deleteButton = new WebInspector.StatusBarButton(WebInspector.UIString("Delete"), "delete-storage-status-bar-item");
    this.deleteButton.visible = false;
    this.deleteButton.addEventListener("click", this._deleteButtonClicked.bind(this), false);

    this.refreshButton = new WebInspector.StatusBarButton(WebInspector.UIString("Refresh"), "refresh-storage-status-bar-item");
    this.refreshButton.addEventListener("click", this._refreshButtonClicked.bind(this), false);
    
    this._cookieDomain = cookieDomain;
}

WebInspector.CookieItemsView.prototype = {
    get statusBarItems()
    {
        return [this.refreshButton.element, this.deleteButton.element];
    },

    show: function(parentElement)
    {
        WebInspector.View.prototype.show.call(this, parentElement);
        this.update();
    },

    hide: function()
    {
        WebInspector.View.prototype.hide.call(this);
        this.deleteButton.visible = false;
    },

    update: function()
    {
        this.element.removeChildren();

        var self = this;
        function callback(cookies, isAdvanced) {
            var dataGrid = (isAdvanced ? self.dataGridForCookies(cookies) : self.simpleDataGridForCookies(cookies));
            if (dataGrid) {
                self._dataGrid = dataGrid;
                self.element.appendChild(dataGrid.element);
                self._dataGrid.updateWidths();
                if (isAdvanced)
                    self.deleteButton.visible = true;
            } else {
                var emptyMsgElement = document.createElement("div");
                emptyMsgElement.className = "storage-table-empty";
                emptyMsgElement.textContent = WebInspector.UIString("This site has no cookies.");
                self.element.appendChild(emptyMsgElement);
                self._dataGrid = null;
                self.deleteButton.visible = false;
            }
        }

        WebInspector.Cookies.getCookiesAsync(callback, this._cookieDomain);
    },

    dataGridForCookies: function(cookies)
    {
        if (!cookies.length)
            return null;

        for (var i = 0; i < cookies.length; ++i)
            cookies[i].expires = new Date(cookies[i].expires);

        var columns = { 0: {}, 1: {}, 2: {}, 3: {}, 4: {}, 5: {}, 6: {}, 7: {} };
        columns[0].title = WebInspector.UIString("Name");
        columns[0].width = columns[0].title.length;
        columns[1].title = WebInspector.UIString("Value");
        columns[1].width = columns[1].title.length;
        columns[2].title = WebInspector.UIString("Domain");
        columns[2].width = columns[2].title.length;
        columns[3].title = WebInspector.UIString("Path");
        columns[3].width = columns[3].title.length;
        columns[4].title = WebInspector.UIString("Expires");
        columns[4].width = columns[4].title.length;
        columns[5].title = WebInspector.UIString("Size");
        columns[5].width = columns[5].title.length;
        columns[5].aligned = "right";
        columns[6].title = WebInspector.UIString("HTTP");
        columns[6].width = columns[6].title.length;
        columns[6].aligned = "centered";
        columns[7].title = WebInspector.UIString("Secure");
        columns[7].width = columns[7].title.length;
        columns[7].aligned = "centered";

        function updateDataAndColumn(index, value) {
            data[index] = value;
            if (value.length > columns[index].width)
                columns[index].width = value.length;
        }

        var data;
        var nodes = [];
        for (var i = 0; i < cookies.length; ++i) {
            var cookie = cookies[i];
            data = {};

            updateDataAndColumn(0, cookie.name);
            updateDataAndColumn(1, cookie.value);
            updateDataAndColumn(2, cookie.domain);
            updateDataAndColumn(3, cookie.path);
            updateDataAndColumn(4, (cookie.session ? WebInspector.UIString("Session") : cookie.expires.toGMTString()));
            updateDataAndColumn(5, Number.bytesToString(cookie.size, WebInspector.UIString));
            updateDataAndColumn(6, (cookie.httpOnly ? "\u2713" : "")); // Checkmark
            updateDataAndColumn(7, (cookie.secure ? "\u2713" : "")); // Checkmark

            var node = new WebInspector.DataGridNode(data, false);
            node.cookie = cookie;
            node.selectable = true;
            nodes.push(node);
        }

        var totalColumnWidths = 0;
        for (var columnIdentifier in columns)
            totalColumnWidths += columns[columnIdentifier].width;

        // Enforce the Value column (the 2nd column) to be a max of 33%
        // tweaking the raw total width because may massively outshadow the others
        var valueColumnWidth = columns[1].width;
        if (valueColumnWidth / totalColumnWidths > 0.33) {
            totalColumnWidths -= valueColumnWidth;
            totalColumnWidths *= 1.33;
            columns[1].width = totalColumnWidths * 0.33;
        }

        // Calculate the percentage width for the columns.
        const minimumPrecent = 6;
        var recoupPercent = 0;
        for (var columnIdentifier in columns) {
            var width = columns[columnIdentifier].width;
            width = Math.round((width / totalColumnWidths) * 100);
            if (width < minimumPrecent) {
                recoupPercent += (minimumPrecent - width);
                width = minimumPrecent;
            }
            columns[columnIdentifier].width = width;
        }

        // Enforce the minimum percentage width. (need to narrow total percentage due to earlier additions)
        while (recoupPercent > 0) {
            for (var columnIdentifier in columns) {
                if (columns[columnIdentifier].width > minimumPrecent) {
                    --columns[columnIdentifier].width;
                    --recoupPercent;
                    if (!recoupPercent)
                        break;
                }
            }
        }

        for (var columnIdentifier in columns)
            columns[columnIdentifier].width += "%";

        var dataGrid = new WebInspector.DataGrid(columns, null, this._deleteCookieCallback.bind(this));
        var length = nodes.length;
        for (var i = 0; i < length; ++i)
            dataGrid.appendChild(nodes[i]);
        if (length > 0)
            nodes[0].selected = true;

        return dataGrid;
    },

    simpleDataGridForCookies: function(cookies)
    {
        if (!cookies.length)
            return null;

        var columns = {};
        columns[0] = {};
        columns[1] = {};
        columns[0].title = WebInspector.UIString("Name");
        columns[0].width = columns[0].title.length;
        columns[1].title = WebInspector.UIString("Value");
        columns[1].width = columns[1].title.length;

        var nodes = [];
        for (var i = 0; i < cookies.length; ++i) {
            var cookie = cookies[i];
            var data = {};

            var name = cookie.name;
            data[0] = name;
            if (name.length > columns[0].width)
                columns[0].width = name.length;

            var value = cookie.value;
            data[1] = value;
            if (value.length > columns[1].width)
                columns[1].width = value.length;

            var node = new WebInspector.DataGridNode(data, false);
            node.selectable = true;
            nodes.push(node);
        }

        var totalColumnWidths = columns[0].width + columns[1].width;
        var width = Math.round((columns[0].width * 100) / totalColumnWidths);
        const minimumPrecent = 20;
        if (width < minimumPrecent)
            width = minimumPrecent;
        if (width > 100 - minimumPrecent)
            width = 100 - minimumPrecent;
        columns[0].width = width;
        columns[1].width = 100 - width;
        columns[0].width += "%";
        columns[1].width += "%";

        var dataGrid = new WebInspector.DataGrid(columns);
        var length = nodes.length;
        for (var i = 0; i < length; ++i)
            dataGrid.appendChild(nodes[i]);
        if (length > 0)
            nodes[0].selected = true;

        return dataGrid;
    },
    
    resize: function()
    {
        if (this._dataGrid)
            this._dataGrid.updateWidths();
    },

    _deleteButtonClicked: function(event)
    {
        if (!this._dataGrid || !this._dataGrid.selectedNode)
            return;

        this._deleteCookieCallback(this._dataGrid.selectedNode);
    },
    
    _deleteCookieCallback: function(node)
    {
        var cookie = node.cookie;
        InspectorController.deleteCookie(cookie.name, this._cookieDomain);
        this.update();
    },

    _refreshButtonClicked: function(event)
    {
        this.update();
    }
}

WebInspector.CookieItemsView.prototype.__proto__ = WebInspector.View.prototype;

/* Script.js */

/*
 * Copyright (C) 2008 Apple Inc. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.Script = function(sourceID, sourceURL, source, startingLine, errorLine, errorMessage)
{
    this.sourceID = sourceID;
    this.sourceURL = sourceURL;
    this.source = source;
    this.startingLine = startingLine;
    this.errorLine = errorLine;
    this.errorMessage = errorMessage;

    // if no URL, look for "//@ sourceURL=" decorator
    // note that this sourceURL comment decorator is behavior that FireBug added
    // in it's 1.1 release as noted in the release notes:
    // http://fbug.googlecode.com/svn/branches/firebug1.1/docs/ReleaseNotes_1.1.txt
    if (!sourceURL) {
        // use of [ \t] rather than \s is to prevent \n from matching
        var pattern = /^\s*\/\/[ \t]*@[ \t]*sourceURL[ \t]*=[ \t]*(\S+).*$/m;
        var match = pattern.exec(source);

        if (match)
            this.sourceURL = WebInspector.UIString("(program): %s", match[1]);
    }
}

WebInspector.Script.prototype = {
}

/* Breakpoint.js */

/*
 * Copyright (C) 2008 Apple Inc. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.Breakpoint = function(url, line, sourceID, condition)
{
    this.url = url;
    this.line = line;
    this.sourceID = sourceID;
    this._enabled = true;
    this._sourceText = "";
    this._condition = condition || "";
}

WebInspector.Breakpoint.prototype = {
    get enabled()
    {
        return this._enabled;
    },

    set enabled(x)
    {
        if (this._enabled === x)
            return;

        this._enabled = x;

        if (this._enabled)
            this.dispatchEventToListeners("enabled");
        else
            this.dispatchEventToListeners("disabled");
    },

    get sourceText()
    {
        return this._sourceText;
    },

    set sourceText(text)
    {
        this._sourceText = text;
        this.dispatchEventToListeners("text-changed");
    },

    get label()
    {
        var displayName = (this.url ? WebInspector.displayNameForURL(this.url) : WebInspector.UIString("(program)"));
        return displayName + ":" + this.line;
    },

    get id()
    {
        return this.sourceID + ":" + this.line;
    },

    get condition()
    {
        return this._condition;
    },

    set condition(c)
    {
        c = c || "";
        if (this._condition === c)
            return;

        this._condition = c;
        this.dispatchEventToListeners("condition-changed");

        if (this.enabled)
            InspectorController.updateBreakpoint(this.sourceID, this.line, c);
    }
}

WebInspector.Breakpoint.prototype.__proto__ = WebInspector.Object.prototype;

/* SidebarPane.js */

/*
 * Copyright (C) 2007 Apple Inc.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.SidebarPane = function(title)
{
    this.element = document.createElement("div");
    this.element.className = "pane";

    this.titleElement = document.createElement("div");
    this.titleElement.className = "title";
    this.titleElement.addEventListener("click", this.toggleExpanded.bind(this), false);

    this.bodyElement = document.createElement("div");
    this.bodyElement.className = "body";

    this.element.appendChild(this.titleElement);
    this.element.appendChild(this.bodyElement);

    this.title = title;
    this.growbarVisible = false;
    this.expanded = false;
}

WebInspector.SidebarPane.prototype = {
    get title()
    {
        return this._title;
    },

    set title(x)
    {
        if (this._title === x)
            return;
        this._title = x;
        this.titleElement.textContent = x;
    },

    get growbarVisible()
    {
        return this._growbarVisible;
    },

    set growbarVisible(x)
    {
        if (this._growbarVisible === x)
            return;

        this._growbarVisible = x;

        if (x && !this._growbarElement) {
            this._growbarElement = document.createElement("div");
            this._growbarElement.className = "growbar";
            this.element.appendChild(this._growbarElement);
        } else if (!x && this._growbarElement) {
            if (this._growbarElement.parentNode)
                this._growbarElement.parentNode(this._growbarElement);
            delete this._growbarElement;
        }
    },

    get expanded()
    {
        return this._expanded;
    },

    set expanded(x)
    {
        if (x)
            this.expand();
        else
            this.collapse();
    },

    expand: function()
    {
        if (this._expanded)
            return;
        this._expanded = true;
        this.element.addStyleClass("expanded");
        if (this.onexpand)
            this.onexpand(this);
    },

    collapse: function()
    {
        if (!this._expanded)
            return;
        this._expanded = false;
        this.element.removeStyleClass("expanded");
        if (this.oncollapse)
            this.oncollapse(this);
    },

    toggleExpanded: function()
    {
        this.expanded = !this.expanded;
    }
}

WebInspector.SidebarPane.prototype.__proto__ = WebInspector.Object.prototype;

/* ElementsTreeOutline.js */

/*
 * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
 * Copyright (C) 2008 Matt Lilek <webkit@mattlilek.com>
 * Copyright (C) 2009 Joseph Pecoraro
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.ElementsTreeOutline = function() {
    this.element = document.createElement("ol");
    this.element.addEventListener("mousedown", this._onmousedown.bind(this), false);
    this.element.addEventListener("mousemove", this._onmousemove.bind(this), false);
    this.element.addEventListener("mouseout", this._onmouseout.bind(this), false);

    TreeOutline.call(this, this.element);

    this.includeRootDOMNode = true;
    this.selectEnabled = false;
    this.showInElementsPanelEnabled = false;
    this.rootDOMNode = null;
    this.focusedDOMNode = null;
}

WebInspector.ElementsTreeOutline.prototype = {
    get rootDOMNode()
    {
        return this._rootDOMNode;
    },

    set rootDOMNode(x)
    {
        if (this._rootDOMNode === x)
            return;

        this._rootDOMNode = x;

        this.update();
    },

    get focusedDOMNode()
    {
        return this._focusedDOMNode;
    },

    set focusedDOMNode(x)
    {
        if (this._focusedDOMNode === x) {
            this.revealAndSelectNode(x);
            return;
        }

        this._focusedDOMNode = x;

        this.revealAndSelectNode(x);

        // The revealAndSelectNode() method might find a different element if there is inlined text,
        // and the select() call would change the focusedDOMNode and reenter this setter. So to
        // avoid calling focusedNodeChanged() twice, first check if _focusedDOMNode is the same
        // node as the one passed in.
        if (this._focusedDOMNode === x) {
            this.focusedNodeChanged();

            if (x && !this.suppressSelectHighlight) {
                InspectorController.highlightDOMNode(x.id);

                if ("_restorePreviousHighlightNodeTimeout" in this)
                    clearTimeout(this._restorePreviousHighlightNodeTimeout);

                function restoreHighlightToHoveredNode()
                {
                    var hoveredNode = WebInspector.hoveredDOMNode;
                    if (hoveredNode)
                        InspectorController.highlightDOMNode(hoveredNode.id);
                    else
                        InspectorController.hideDOMNodeHighlight();
                }

                this._restorePreviousHighlightNodeTimeout = setTimeout(restoreHighlightToHoveredNode, 2000);
            }
        }
    },

    update: function()
    {
        var selectedNode = this.selectedTreeElement ? this.selectedTreeElement.representedObject : null;

        this.removeChildren();

        if (!this.rootDOMNode)
            return;

        var treeElement;
        if (this.includeRootDOMNode) {
            treeElement = new WebInspector.ElementsTreeElement(this.rootDOMNode);
            treeElement.selectable = this.selectEnabled;
            this.appendChild(treeElement);
        } else {
            // FIXME: this could use findTreeElement to reuse a tree element if it already exists
            var node = this.rootDOMNode.firstChild;
            while (node) {
                treeElement = new WebInspector.ElementsTreeElement(node);
                treeElement.selectable = this.selectEnabled;
                this.appendChild(treeElement);
                node = node.nextSibling;
            }
        }

        if (selectedNode)
            this.revealAndSelectNode(selectedNode);
    },

    updateSelection: function()
    {
        if (!this.selectedTreeElement)
            return;
        var element = this.treeOutline.selectedTreeElement;
        element.updateSelection();
    },

    focusedNodeChanged: function(forceUpdate) {},

    findTreeElement: function(node)
    {
        var treeElement = TreeOutline.prototype.findTreeElement.call(this, node, isAncestorNode, parentNode);
        if (!treeElement && node.nodeType === Node.TEXT_NODE) {
            // The text node might have been inlined if it was short, so try to find the parent element.
            treeElement = TreeOutline.prototype.findTreeElement.call(this, node.parentNode, isAncestorNode, parentNode);
        }

        return treeElement;
    },

    revealAndSelectNode: function(node)
    {
        if (!node)
            return;

        var treeElement = this.findTreeElement(node);
        if (!treeElement)
            return;

        treeElement.reveal();
        treeElement.select();
    },

    _treeElementFromEvent: function(event)
    {
        var root = this.element;

        // We choose this X coordinate based on the knowledge that our list
        // items extend nearly to the right edge of the outer <ol>.
        var x = root.totalOffsetLeft + root.offsetWidth - 20;

        var y = event.pageY;

        // Our list items have 1-pixel cracks between them vertically. We avoid
        // the cracks by checking slightly above and slightly below the mouse
        // and seeing if we hit the same element each time.
        var elementUnderMouse = this.treeElementFromPoint(x, y);
        var elementAboveMouse = this.treeElementFromPoint(x, y - 2);
        var element;
        if (elementUnderMouse === elementAboveMouse)
            element = elementUnderMouse;
        else
            element = this.treeElementFromPoint(x, y + 2);

        return element;
    },
    
    handleKeyEvent: function(event)
    {
        var selectedElement = this.selectedTreeElement;
        if (!selectedElement)
            return;

        // Delete or backspace pressed, delete the node.
        if (event.keyCode === 8 || event.keyCode === 46) {
            selectedElement.remove();
            event.preventDefault();
            return;
        }

        // On Enter or Return start editing the first attribute
        // or create a new attribute on the selected element.
        if (event.keyIdentifier === "Enter") {
            if (this._editing)
                return;

            selectedElement._startEditing();

            // prevent a newline from being immediately inserted
            event.preventDefault();
            return;
        }

        TreeOutline.prototype.handleKeyEvent.call(this, event);
    },

    _onmousedown: function(event)
    {
        var element = this._treeElementFromEvent(event);

        if (!element || element.isEventWithinDisclosureTriangle(event))
            return;

        element.select();
    },

    _onmousemove: function(event)
    {
        var element = this._treeElementFromEvent(event);
        if (element && this._previousHoveredElement === element)
            return;

        if (this._previousHoveredElement) {
            this._previousHoveredElement.hovered = false;
            delete this._previousHoveredElement;
        }

        if (element && !element.elementCloseTag) {
            element.hovered = true;
            this._previousHoveredElement = element;
        }

        WebInspector.hoveredDOMNode = (element && !element.elementCloseTag ? element.representedObject : null);
    },

    _onmouseout: function(event)
    {
        var nodeUnderMouse = document.elementFromPoint(event.pageX, event.pageY);
        if (nodeUnderMouse && nodeUnderMouse.isDescendant(this.element))
            return;

        if (this._previousHoveredElement) {
            this._previousHoveredElement.hovered = false;
            delete this._previousHoveredElement;
        }

        WebInspector.hoveredDOMNode = null;
    }
}

WebInspector.ElementsTreeOutline.prototype.__proto__ = TreeOutline.prototype;

WebInspector.ElementsTreeElement = function(node)
{
    var hasChildrenOverride = node.hasChildNodes() && !this._showInlineText(node);

    // The title will be updated in onattach.
    TreeElement.call(this, "", node, hasChildrenOverride);

    if (this.representedObject.nodeType == Node.ELEMENT_NODE)
        this._canAddAttributes = true;
}

WebInspector.ElementsTreeElement.prototype = {
    get highlighted()
    {
        return this._highlighted;
    },

    set highlighted(x)
    {
        if (this._highlighted === x)
            return;

        this._highlighted = x;

        if (this.listItemElement) {
            if (x)
                this.listItemElement.addStyleClass("highlighted");
            else
                this.listItemElement.removeStyleClass("highlighted");
        }
    },

    get hovered()
    {
        return this._hovered;
    },

    set hovered(x)
    {
        if (this._hovered === x)
            return;

        this._hovered = x;

        if (this.listItemElement) {
            if (x) {
                this.updateSelection();
                this.listItemElement.addStyleClass("hovered");
                if (this._canAddAttributes)
                    this._pendingToggleNewAttribute = setTimeout(this.toggleNewAttributeButton.bind(this, true), 500);
            } else {
                this.listItemElement.removeStyleClass("hovered");
                if (this._pendingToggleNewAttribute) {
                    clearTimeout(this._pendingToggleNewAttribute);
                    delete this._pendingToggleNewAttribute;
                }
                this.toggleNewAttributeButton(false);
            }
        }
    },

    toggleNewAttributeButton: function(visible)
    {
        function removeAddAttributeSpan()
        {
            if (this._addAttributeElement && this._addAttributeElement.parentNode)
                this._addAttributeElement.parentNode.removeChild(this._addAttributeElement);
            delete this._addAttributeElement;

            this.updateSelection();
        }

        if (!this._addAttributeElement && visible && !this._editing) {
            var span = document.createElement("span");
            span.className = "add-attribute webkit-html-attribute-name";
            span.textContent = " ?=\"\"";
            span.addEventListener("dblclick", removeAddAttributeSpan.bind(this), false);
            this._addAttributeElement = span;

            var tag = this.listItemElement.getElementsByClassName("webkit-html-tag")[0];
            this._insertInLastAttributePosition(tag, span);
        } else if (!visible && this._addAttributeElement)
            removeAddAttributeSpan.call(this);
    },

    updateSelection: function()
    {
        var listItemElement = this.listItemElement;
        if (!listItemElement)
            return;

        if (document.body.offsetWidth <= 0) {
            // The stylesheet hasn't loaded yet or the window is closed,
            // so we can't calculate what is need. Return early.
            return;
        }

        if (!this.selectionElement) {
            this.selectionElement = document.createElement("div");
            this.selectionElement.className = "selection selected";
            listItemElement.insertBefore(this.selectionElement, listItemElement.firstChild);
        }

        this.selectionElement.style.height = listItemElement.offsetHeight + "px";
    },

    onattach: function()
    {
        this.listItemElement.addEventListener("mousedown", this.onmousedown.bind(this), false);

        if (this._highlighted)
            this.listItemElement.addStyleClass("highlighted");

        if (this._hovered) {
            this.updateSelection();
            this.listItemElement.addStyleClass("hovered");
        }

        this._updateTitle();

        this._preventFollowingLinksOnDoubleClick();
    },

    _preventFollowingLinksOnDoubleClick: function()
    {
        var links = this.listItemElement.querySelectorAll("li > .webkit-html-tag > .webkit-html-attribute > .webkit-html-external-link, li > .webkit-html-tag > .webkit-html-attribute > .webkit-html-resource-link");
        if (!links)
            return;

        for (var i = 0; i < links.length; ++i)
            links[i].preventFollowOnDoubleClick = true;
    },

    onpopulate: function()
    {
        if (this.children.length || this._showInlineText(this.representedObject))
            return;

        this.updateChildren();
    },
    
    updateChildren: function(fullRefresh)
    {
        WebInspector.domAgent.getChildNodesAsync(this.representedObject, this._updateChildren.bind(this, fullRefresh));
    },

    _updateChildren: function(fullRefresh)
    {
        if (fullRefresh) {
            var selectedTreeElement = this.treeOutline.selectedTreeElement;
            if (selectedTreeElement && selectedTreeElement.hasAncestor(this))
                this.select();
            this.removeChildren();
        }

        var treeElement = this;
        var treeChildIndex = 0;

        function updateChildrenOfNode(node)
        {
            var treeOutline = treeElement.treeOutline;
            var child = node.firstChild;
            while (child) {
                var currentTreeElement = treeElement.children[treeChildIndex];
                if (!currentTreeElement || currentTreeElement.representedObject !== child) {
                    // Find any existing element that is later in the children list.
                    var existingTreeElement = null;
                    for (var i = (treeChildIndex + 1); i < treeElement.children.length; ++i) {
                        if (treeElement.children[i].representedObject === child) {
                            existingTreeElement = treeElement.children[i];
                            break;
                        }
                    }

                    if (existingTreeElement && existingTreeElement.parent === treeElement) {
                        // If an existing element was found and it has the same parent, just move it.
                        var wasSelected = existingTreeElement.selected;
                        treeElement.removeChild(existingTreeElement);
                        treeElement.insertChild(existingTreeElement, treeChildIndex);
                        if (wasSelected)
                            existingTreeElement.select();
                    } else {
                        // No existing element found, insert a new element.
                        var newElement = new WebInspector.ElementsTreeElement(child);
                        newElement.selectable = treeOutline.selectEnabled;
                        treeElement.insertChild(newElement, treeChildIndex);
                    }
                }

                child = child.nextSibling;
                ++treeChildIndex;
            }
        }

        // Remove any tree elements that no longer have this node (or this node's contentDocument) as their parent.
        for (var i = (this.children.length - 1); i >= 0; --i) {
            if ("elementCloseTag" in this.children[i])
                continue;

            var currentChild = this.children[i];
            var currentNode = currentChild.representedObject;
            var currentParentNode = currentNode.parentNode;

            if (currentParentNode === this.representedObject)
                continue;

            var selectedTreeElement = this.treeOutline.selectedTreeElement;
            if (selectedTreeElement && (selectedTreeElement === currentChild || selectedTreeElement.hasAncestor(currentChild)))
                this.select();

            this.removeChildAtIndex(i);
        }

        updateChildrenOfNode(this.representedObject);

        var lastChild = this.children[this.children.length - 1];
        if (this.representedObject.nodeType == Node.ELEMENT_NODE && (!lastChild || !lastChild.elementCloseTag)) {
            var title = "<span class=\"webkit-html-tag close\">&lt;/" + this.representedObject.nodeName.toLowerCase().escapeHTML() + "&gt;</span>";
            var item = new TreeElement(title, null, false);
            item.selectable = false;
            item.elementCloseTag = true;
            this.appendChild(item);
        }
    },

    onexpand: function()
    {
        this.treeOutline.updateSelection();
    },

    oncollapse: function()
    {
        this.treeOutline.updateSelection();
    },

    onreveal: function()
    {
        if (this.listItemElement)
            this.listItemElement.scrollIntoViewIfNeeded(false);
    },

    onselect: function()
    {
        this.treeOutline.focusedDOMNode = this.representedObject;
        this.updateSelection();
    },

    onmousedown: function(event)
    {
        if (this._editing)
            return;

        if (this.isEventWithinDisclosureTriangle(event))
            return;

        if (this.treeOutline.showInElementsPanelEnabled) {    
            WebInspector.showElementsPanel();
            WebInspector.panels.elements.focusedDOMNode = this.representedObject;
        }

        // Prevent selecting the nearest word on double click.
        if (event.detail >= 2)
            event.preventDefault();
    },

    ondblclick: function(treeElement, event)
    {
        if (this._editing)
            return;

        if (this._startEditingFromEvent(event, treeElement))
            return;

        if (this.hasChildren && !this.expanded)
            this.expand();
    },

    _insertInLastAttributePosition: function(tag, node)
    {
        if (tag.getElementsByClassName("webkit-html-attribute").length > 0)
            tag.insertBefore(node, tag.lastChild);
        else {
            var nodeName = tag.textContent.match(/^<(.*?)>$/)[1];
            tag.textContent = '';
            tag.appendChild(document.createTextNode('<'+nodeName));
            tag.appendChild(node);
            tag.appendChild(document.createTextNode('>'));
        }

        this.updateSelection();
    },

    _startEditingFromEvent: function(event, treeElement)
    {
        if (this.treeOutline.focusedDOMNode != this.representedObject)
            return;

        if (this.representedObject.nodeType != Node.ELEMENT_NODE && this.representedObject.nodeType != Node.TEXT_NODE)
            return false;

        var textNode = event.target.enclosingNodeOrSelfWithClass("webkit-html-text-node");
        if (textNode)
            return this._startEditingTextNode(textNode);

        var attribute = event.target.enclosingNodeOrSelfWithClass("webkit-html-attribute");
        if (attribute)
            return this._startEditingAttribute(attribute, event.target);

        var newAttribute = event.target.enclosingNodeOrSelfWithClass("add-attribute");
        if (newAttribute)
            return this._addNewAttribute(treeElement.listItemElement);

        return false;
    },

    _startEditing: function()
    {
        if (this.treeOutline.focusedDOMNode !== this.representedObject)
            return;

        var listItem = this._listItemNode;

        if (this._canAddAttributes) {
            this.toggleNewAttributeButton(false);
            var attribute = listItem.getElementsByClassName("webkit-html-attribute")[0];
            if (attribute)
                return this._startEditingAttribute(attribute, attribute.getElementsByClassName("webkit-html-attribute-value")[0]);

            return this._addNewAttribute(listItem);
        }

        if (this.representedObject.nodeType === Node.TEXT_NODE) {
            var textNode = listItem.getElementsByClassName("webkit-html-text-node")[0];
            if (textNode)
                return this._startEditingTextNode(textNode);
            return;
        }
    },

    _addNewAttribute: function(listItemElement)
    {
        var attr = document.createElement("span");
        attr.className = "webkit-html-attribute";
        attr.style.marginLeft = "2px"; // overrides the .editing margin rule
        attr.style.marginRight = "2px"; // overrides the .editing margin rule
        var name = document.createElement("span");
        name.className = "webkit-html-attribute-name new-attribute";
        name.textContent = " ";
        var value = document.createElement("span");
        value.className = "webkit-html-attribute-value";
        attr.appendChild(name);
        attr.appendChild(value);

        var tag = listItemElement.getElementsByClassName("webkit-html-tag")[0];
        this._insertInLastAttributePosition(tag, attr);
        return this._startEditingAttribute(attr, attr);
    },

    _triggerEditAttribute: function(attributeName)
    {
        var attributeElements = this.listItemElement.getElementsByClassName("webkit-html-attribute-name");
        for (var i = 0, len = attributeElements.length; i < len; ++i) {
            if (attributeElements[i].textContent === attributeName) {
                for (var elem = attributeElements[i].nextSibling; elem; elem = elem.nextSibling) {
                    if (elem.nodeType !== Node.ELEMENT_NODE)
                        continue;

                    if (elem.hasStyleClass("webkit-html-attribute-value"))
                        return this._startEditingAttribute(attributeElements[i].parentNode, elem);
                }
            }
        }
    },

    _startEditingAttribute: function(attribute, elementForSelection)
    {
        if (WebInspector.isBeingEdited(attribute))
            return true;

        var attributeNameElement = attribute.getElementsByClassName("webkit-html-attribute-name")[0];
        if (!attributeNameElement)
            return false;

        var attributeName = attributeNameElement.innerText;

        function removeZeroWidthSpaceRecursive(node)
        {
            if (node.nodeType === Node.TEXT_NODE) {
                node.nodeValue = node.nodeValue.replace(/\u200B/g, "");
                return;
            }

            if (node.nodeType !== Node.ELEMENT_NODE)
                return;

            for (var child = node.firstChild; child; child = child.nextSibling)
                removeZeroWidthSpaceRecursive(child);
        }

        // Remove zero-width spaces that were added by nodeTitleInfo.
        removeZeroWidthSpaceRecursive(attribute);

        this._editing = true;

        WebInspector.startEditing(attribute, this._attributeEditingCommitted.bind(this), this._editingCancelled.bind(this), attributeName);
        window.getSelection().setBaseAndExtent(elementForSelection, 0, elementForSelection, 1);

        return true;
    },

    _startEditingTextNode: function(textNode)
    {
        if (WebInspector.isBeingEdited(textNode))
            return true;

        this._editing = true;

        WebInspector.startEditing(textNode, this._textNodeEditingCommitted.bind(this), this._editingCancelled.bind(this));
        window.getSelection().setBaseAndExtent(textNode, 0, textNode, 1);

        return true;
    },

    _attributeEditingCommitted: function(element, newText, oldText, attributeName, moveDirection)
    {
        delete this._editing;

        // Before we do anything, determine where we should move
        // next based on the current element's settings
        var moveToAttribute;
        var newAttribute;
        if (moveDirection) {
            var found = false;
            var attributes = this.representedObject.attributes;
            for (var i = 0, len = attributes.length; i < len; ++i) {
                if (attributes[i].name === attributeName) {
                    found = true;
                    if (moveDirection === "backward" && i > 0)
                        moveToAttribute = attributes[i - 1].name;
                    else if (moveDirection === "forward" && i < attributes.length - 1)
                        moveToAttribute = attributes[i + 1].name;
                    else if (moveDirection === "forward" && i === attributes.length - 1)
                        newAttribute = true;
                }
            }

            if (!found && moveDirection === "backward" && attributes.length > 0)
                moveToAttribute = attributes[attributes.length - 1].name;
            else if (!found && moveDirection === "forward" && !/^\s*$/.test(newText))
                newAttribute = true;
        }

        function moveToNextAttributeIfNeeded() {
            if (moveToAttribute)
                this._triggerEditAttribute(moveToAttribute);
            else if (newAttribute)
                this._addNewAttribute(this.listItemElement);
        }

        var parseContainerElement = document.createElement("span");
        parseContainerElement.innerHTML = "<span " + newText + "></span>";
        var parseElement = parseContainerElement.firstChild;

        if (!parseElement) {
            this._editingCancelled(element, attributeName);
            moveToNextAttributeIfNeeded.call(this);
            return;
        }

        if (!parseElement.hasAttributes()) {
            this.representedObject.removeAttribute(attributeName);
            moveToNextAttributeIfNeeded.call(this);
            return;
        }

        var foundOriginalAttribute = false;
        for (var i = 0; i < parseElement.attributes.length; ++i) {
            var attr = parseElement.attributes[i];
            foundOriginalAttribute = foundOriginalAttribute || attr.name === attributeName;
            try {
                this.representedObject.setAttribute(attr.name, attr.value);
            } catch(e) {} // ignore invalid attribute (innerHTML doesn't throw errors, but this can)
        }

        if (!foundOriginalAttribute)
            this.representedObject.removeAttribute(attributeName);

        this.treeOutline.focusedNodeChanged(true);

        moveToNextAttributeIfNeeded.call(this);
    },

    _textNodeEditingCommitted: function(element, newText)
    {
        delete this._editing;

        var textNode;
        if (this.representedObject.nodeType == Node.ELEMENT_NODE) {
            // We only show text nodes inline in elements if the element only
            // has a single child, and that child is a text node.
            textNode = this.representedObject.firstChild;
        } else if (this.representedObject.nodeType == Node.TEXT_NODE)
            textNode = this.representedObject;

        textNode.nodeValue = newText;

        // No need to call _updateTitle here, it will be called after the nodeValue is committed.
    },

    _editingCancelled: function(element, context)
    {
        delete this._editing;

        // No need to call _updateTitle here, the editing code will revert to the original text.
    },

    _updateTitle: function()
    {
        // If we are editing, return early to prevent canceling the edit.
        // After editing is committed _updateTitle will be called.
        if (this._editing)
            return;

        var title = this._nodeTitleInfo(this.representedObject, this.hasChildren, WebInspector.linkifyURL).title;
        this.title = "<span class=\"highlight\">" + title + "</span>";
        delete this.selectionElement;
        this.updateSelection();
        this._preventFollowingLinksOnDoubleClick();
    },

    _nodeTitleInfo: function(node, hasChildren, linkify)
    {
        var info = {title: "", hasChildren: hasChildren};
        
        switch (node.nodeType) {
            case Node.DOCUMENT_NODE:
                info.title = "Document";
                break;
                
            case Node.ELEMENT_NODE:
                info.title = "<span class=\"webkit-html-tag\">&lt;" + node.nodeName.toLowerCase().escapeHTML();
                
                if (node.hasAttributes()) {
                    for (var i = 0; i < node.attributes.length; ++i) {
                        var attr = node.attributes[i];
                        info.title += " <span class=\"webkit-html-attribute\"><span class=\"webkit-html-attribute-name\">" + attr.name.escapeHTML() + "</span>=&#8203;\"";
                        
                        var value = attr.value;
                        if (linkify && (attr.name === "src" || attr.name === "href")) {
                            var value = value.replace(/([\/;:\)\]\}])/g, "$1\u200B");
                            info.title += linkify(attr.value, value, "webkit-html-attribute-value", node.nodeName.toLowerCase() == "a");
                        } else {
                            var value = value.escapeHTML();
                            value = value.replace(/([\/;:\)\]\}])/g, "$1&#8203;");
                            info.title += "<span class=\"webkit-html-attribute-value\">" + value + "</span>";
                        }
                        info.title += "\"</span>";
                    }
                }
                info.title += "&gt;</span>&#8203;";
                
                // If this element only has a single child that is a text node,
                // just show that text and the closing tag inline rather than
                // create a subtree for them
                
                var textChild = onlyTextChild.call(node);
                var showInlineText = textChild && textChild.textContent.length < Preferences.maxInlineTextChildLength;
                
                if (showInlineText) {
                    info.title += "<span class=\"webkit-html-text-node\">" + textChild.nodeValue.escapeHTML() + "</span>&#8203;<span class=\"webkit-html-tag\">&lt;/" + node.nodeName.toLowerCase().escapeHTML() + "&gt;</span>";
                    info.hasChildren = false;
                }
                break;
                
            case Node.TEXT_NODE:
                if (isNodeWhitespace.call(node))
                    info.title = "(whitespace)";
                else {
                    if (node.parentNode && node.parentNode.nodeName.toLowerCase() == "script") {
                        var newNode = document.createElement("span");
                        newNode.textContent = node.textContent;

                        var javascriptSyntaxHighlighter = new WebInspector.JavaScriptSourceSyntaxHighlighter(null, null);
                        javascriptSyntaxHighlighter.syntaxHighlightNode(newNode);
                        
                        info.title = "<span class=\"webkit-html-text-node webkit-html-js-node\">" + newNode.innerHTML.replace(/^[\n\r]*/, "").replace(/\s*$/, "") + "</span>";
                    } else if (node.parentNode && node.parentNode.nodeName.toLowerCase() == "style") {
                        var newNode = document.createElement("span");
                        newNode.textContent = node.textContent;
                        
                        var cssSyntaxHighlighter = new WebInspector.CSSSourceSyntaxHighlighter(null, null);
                        cssSyntaxHighlighter.syntaxHighlightNode(newNode);
                        
                        info.title = "<span class=\"webkit-html-text-node webkit-html-css-node\">" + newNode.innerHTML.replace(/^[\n\r]*/, "").replace(/\s*$/, "") + "</span>";
                    } else {
                        info.title = "\"<span class=\"webkit-html-text-node\">" + node.nodeValue.escapeHTML() + "</span>\""; 
                    }
                } 
                break;
                
            case Node.COMMENT_NODE:
                info.title = "<span class=\"webkit-html-comment\">&lt;!--" + node.nodeValue.escapeHTML() + "--&gt;</span>";
                break;
                
            case Node.DOCUMENT_TYPE_NODE:
                info.title = "<span class=\"webkit-html-doctype\">&lt;!DOCTYPE " + node.nodeName;
                if (node.publicId) {
                    info.title += " PUBLIC \"" + node.publicId + "\"";
                    if (node.systemId)
                        info.title += " \"" + node.systemId + "\"";
                } else if (node.systemId)
                    info.title += " SYSTEM \"" + node.systemId + "\"";
                if (node.internalSubset)
                    info.title += " [" + node.internalSubset + "]";
                info.title += "&gt;</span>";
                break;
            default:
                info.title = node.nodeName.toLowerCase().collapseWhitespace().escapeHTML();
        }
        
        return info;
    },

    _showInlineText: function(node)
    {
        if (node.nodeType === Node.ELEMENT_NODE) {
            var textChild = onlyTextChild.call(node);
            if (textChild && textChild.textContent.length < Preferences.maxInlineTextChildLength)
                return true;
        }
        return false;
    },
    
    remove: function()
    {
        var parentElement = this.parent;
        if (!parentElement)
            return;

        var self = this;
        function removeNodeCallback(removedNodeId)
        {
            // -1 is an error code, which means removing the node from the DOM failed,
            // so we shouldn't remove it from the tree.
            if (removedNodeId === -1)
                return;

            parentElement.removeChild(self);
        }

        var callId = WebInspector.Callback.wrap(removeNodeCallback);
        InspectorController.removeNode(callId, this.representedObject.id);
    }
}

WebInspector.ElementsTreeElement.prototype.__proto__ = TreeElement.prototype;

WebInspector.didRemoveNode = WebInspector.Callback.processCallback;

/* SidebarTreeElement.js */

/*
 * Copyright (C) 2008 Apple Inc. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.SidebarSectionTreeElement = function(title, representedObject, hasChildren)
{
    TreeElement.call(this, title.escapeHTML(), representedObject || {}, hasChildren);
}

WebInspector.SidebarSectionTreeElement.prototype = {
    selectable: false,

    get smallChildren()
    {
        return this._smallChildren;
    },

    set smallChildren(x)
    {
        if (this._smallChildren === x)
            return;

        this._smallChildren = x;

        if (this._smallChildren)
            this._childrenListNode.addStyleClass("small");
        else
            this._childrenListNode.removeStyleClass("small");
    },

    onattach: function()
    {
        this._listItemNode.addStyleClass("sidebar-tree-section");
    },

    onreveal: function()
    {
        if (this.listItemElement)
            this.listItemElement.scrollIntoViewIfNeeded(false);
    }
}

WebInspector.SidebarSectionTreeElement.prototype.__proto__ = TreeElement.prototype;

WebInspector.SidebarTreeElement = function(className, title, subtitle, representedObject, hasChildren)
{
    TreeElement.call(this, "", representedObject || {}, hasChildren);

    if (hasChildren) {
        this.disclosureButton = document.createElement("button");
        this.disclosureButton.className = "disclosure-button";
    }

    if (!this.iconElement) {
        this.iconElement = document.createElement("img");
        this.iconElement.className = "icon";
    }

    this.statusElement = document.createElement("div");
    this.statusElement.className = "status";

    this.titlesElement = document.createElement("div");
    this.titlesElement.className = "titles";

    this.titleElement = document.createElement("span");
    this.titleElement.className = "title";
    this.titlesElement.appendChild(this.titleElement);

    this.subtitleElement = document.createElement("span");
    this.subtitleElement.className = "subtitle";
    this.titlesElement.appendChild(this.subtitleElement);

    this.className = className;
    this.mainTitle = title;
    this.subtitle = subtitle;
}

WebInspector.SidebarTreeElement.prototype = {
    get small()
    {
        return this._small;
    },

    set small(x)
    {
        this._small = x;

        if (this._listItemNode) {
            if (this._small)
                this._listItemNode.addStyleClass("small");
            else
                this._listItemNode.removeStyleClass("small");
        }
    },

    get mainTitle()
    {
        return this._mainTitle;
    },

    set mainTitle(x)
    {
        this._mainTitle = x;
        this.refreshTitles();
    },

    get subtitle()
    {
        return this._subtitle;
    },

    set subtitle(x)
    {
        this._subtitle = x;
        this.refreshTitles();
    },

    get bubbleText()
    {
        return this._bubbleText;
    },

    set bubbleText(x)
    {
        if (!this.bubbleElement) {
            this.bubbleElement = document.createElement("div");
            this.bubbleElement.className = "bubble";
            this.statusElement.appendChild(this.bubbleElement);
        }

        this._bubbleText = x;
        this.bubbleElement.textContent = x;
    },

    refreshTitles: function()
    {
        var mainTitle = this.mainTitle;
        if (this.titleElement.textContent !== mainTitle)
            this.titleElement.textContent = mainTitle;

        var subtitle = this.subtitle;
        if (subtitle) {
            if (this.subtitleElement.textContent !== subtitle)
                this.subtitleElement.textContent = subtitle;
            this.titlesElement.removeStyleClass("no-subtitle");
        } else
            this.titlesElement.addStyleClass("no-subtitle");
    },

    isEventWithinDisclosureTriangle: function(event)
    {
        return event.target === this.disclosureButton;
    },

    onattach: function()
    {
        this._listItemNode.addStyleClass("sidebar-tree-item");

        if (this.className)
            this._listItemNode.addStyleClass(this.className);

        if (this.small)
            this._listItemNode.addStyleClass("small");

        if (this.hasChildren && this.disclosureButton)
            this._listItemNode.appendChild(this.disclosureButton);

        this._listItemNode.appendChild(this.iconElement);
        this._listItemNode.appendChild(this.statusElement);
        this._listItemNode.appendChild(this.titlesElement);
    },

    onreveal: function()
    {
        if (this._listItemNode)
            this._listItemNode.scrollIntoViewIfNeeded(false);
    }
}

WebInspector.SidebarTreeElement.prototype.__proto__ = TreeElement.prototype;

/* PropertiesSection.js */

/*
 * Copyright (C) 2007 Apple Inc.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.PropertiesSection = function(title, subtitle)
{
    this.element = document.createElement("div");
    this.element.className = "section";

    this.headerElement = document.createElement("div");
    this.headerElement.className = "header";

    this.titleElement = document.createElement("div");
    this.titleElement.className = "title";

    this.subtitleElement = document.createElement("div");
    this.subtitleElement.className = "subtitle";

    this.headerElement.appendChild(this.subtitleElement);
    this.headerElement.appendChild(this.titleElement);

    this.headerElement.addEventListener("click", this.toggleExpanded.bind(this), false);

    this.propertiesElement = document.createElement("ol");
    this.propertiesElement.className = "properties";
    this.propertiesTreeOutline = new TreeOutline(this.propertiesElement);
    this.propertiesTreeOutline.section = this;

    this.element.appendChild(this.headerElement);
    this.element.appendChild(this.propertiesElement);

    this.title = title;
    this.subtitle = subtitle;
    this._expanded = false;
}

WebInspector.PropertiesSection.prototype = {
    get title()
    {
        return this._title;
    },

    set title(x)
    {
        if (this._title === x)
            return;
        this._title = x;

        if (x instanceof Node) {
            this.titleElement.removeChildren();
            this.titleElement.appendChild(x);
        } else
          this.titleElement.textContent = x;
    },

    get subtitle()
    {
        return this._subtitle;
    },

    set subtitle(x)
    {
        if (this._subtitle === x)
            return;
        this._subtitle = x;
        this.subtitleElement.innerHTML = x;
    },

    get expanded()
    {
        return this._expanded;
    },

    set expanded(x)
    {
        if (x)
            this.expand();
        else
            this.collapse();
    },

    get populated()
    {
        return this._populated;
    },

    set populated(x)
    {
        this._populated = x;
        if (!x && this.onpopulate && this._expanded) {
            this.onpopulate(this);
            this._populated = true;
        }
    },

    expand: function()
    {
        if (this._expanded)
            return;
        this._expanded = true;
        this.element.addStyleClass("expanded");

        if (!this._populated && this.onpopulate) {
            this.onpopulate(this);
            this._populated = true;
        }
    },

    collapse: function()
    {
        if (!this._expanded)
            return;
        this._expanded = false;
        this.element.removeStyleClass("expanded");
    },

    toggleExpanded: function()
    {
        this.expanded = !this.expanded;
    }
}

/* ObjectProxy.js */

/*
 * Copyright (C) 2009 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.ObjectProxy = function(objectId, path, protoDepth, description, hasChildren)
{
    this.objectId = objectId;
    this.path = path || [];
    this.protoDepth = protoDepth || 0;
    this.description = description;
    this.hasChildren = hasChildren;
}

WebInspector.ObjectProxy.wrapPrimitiveValue = function(value)
{
    var proxy = new WebInspector.ObjectProxy();
    proxy.type = typeof value;
    proxy.description = value;
    return proxy;
}

WebInspector.ObjectPropertyProxy = function(name, value)
{
    this.name = name;
    this.value = value;
}

/* ObjectPropertiesSection.js */

/*
 * Copyright (C) 2008 Apple Inc. All Rights Reserved.
 * Copyright (C) 2009 Joseph Pecoraro
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.ObjectPropertiesSection = function(object, title, subtitle, emptyPlaceholder, ignoreHasOwnProperty, extraProperties, treeElementConstructor)
{
    this.emptyPlaceholder = (emptyPlaceholder || WebInspector.UIString("No Properties"));
    this.object = object;
    this.ignoreHasOwnProperty = ignoreHasOwnProperty;
    this.extraProperties = extraProperties;
    this.treeElementConstructor = treeElementConstructor || WebInspector.ObjectPropertyTreeElement;
    this.editable = true;

    WebInspector.PropertiesSection.call(this, title, subtitle);
}

WebInspector.ObjectPropertiesSection.prototype = {
    onpopulate: function()
    {
        this.update();
    },

    update: function()
    {
        var self = this;
        var callback = function(properties) {
            if (!properties)
                return;
            self.updateProperties(properties);
        };
        InjectedScriptAccess.getProperties(this.object, this.ignoreHasOwnProperty, callback);
    },

    updateProperties: function(properties, rootTreeElementConstructor, rootPropertyComparer)
    {
        if (!rootTreeElementConstructor)
            rootTreeElementConstructor = this.treeElementConstructor;
            
        if (!rootPropertyComparer)
            rootPropertyComparer = WebInspector.ObjectPropertiesSection.CompareProperties;
            
        if (this.extraProperties)
            for (var i = 0; i < this.extraProperties.length; ++i)
                properties.push(this.extraProperties[i]);
                
        properties.sort(rootPropertyComparer);

        this.propertiesTreeOutline.removeChildren();

        for (var i = 0; i < properties.length; ++i)
            this.propertiesTreeOutline.appendChild(new rootTreeElementConstructor(properties[i]));

        if (!this.propertiesTreeOutline.children.length) {
            var title = "<div class=\"info\">" + this.emptyPlaceholder + "</div>";
            var infoElement = new TreeElement(title, null, false);
            this.propertiesTreeOutline.appendChild(infoElement);
        }
    }
}

WebInspector.ObjectPropertiesSection.prototype.__proto__ = WebInspector.PropertiesSection.prototype;

WebInspector.ObjectPropertiesSection.CompareProperties = function(propertyA, propertyB) 
{
    var a = propertyA.name;
    var b = propertyB.name;

    // if used elsewhere make sure to
    //  - convert a and b to strings (not needed here, properties are all strings)
    //  - check if a == b (not needed here, no two properties can be the same)

    var diff = 0;
    var chunk = /^\d+|^\D+/;
    var chunka, chunkb, anum, bnum;
    while (diff === 0) {
        if (!a && b)
            return -1;
        if (!b && a)
            return 1;
        chunka = a.match(chunk)[0];
        chunkb = b.match(chunk)[0];
        anum = !isNaN(chunka);
        bnum = !isNaN(chunkb);
        if (anum && !bnum)
            return -1;
        if (bnum && !anum)
            return 1;
        if (anum && bnum) {
            diff = chunka - chunkb;
            if (diff === 0 && chunka.length !== chunkb.length) {
                if (!+chunka && !+chunkb) // chunks are strings of all 0s (special case)
                    return chunka.length - chunkb.length;
                else
                    return chunkb.length - chunka.length;
            }
        } else if (chunka !== chunkb)
            return (chunka < chunkb) ? -1 : 1;
        a = a.substring(chunka.length);
        b = b.substring(chunkb.length);
    }
    return diff;
}

WebInspector.ObjectPropertyTreeElement = function(property)
{
    this.property = property;

    // Pass an empty title, the title gets made later in onattach.
    TreeElement.call(this, "", null, false);
}

WebInspector.ObjectPropertyTreeElement.prototype = {
    onpopulate: function()
    {
        if (this.children.length && !this.shouldRefreshChildren)
            return;

        var callback = function(properties) {
            this.removeChildren();
            if (!properties)
                return;

            properties.sort(WebInspector.ObjectPropertiesSection.CompareProperties);
            for (var i = 0; i < properties.length; ++i) {
                this.appendChild(new this.treeOutline.section.treeElementConstructor(properties[i]));
            }
        };
        InjectedScriptAccess.getProperties(this.property.value, false, callback.bind(this));
    },

    ondblclick: function(element, event)
    {
        this.startEditing();
    },

    onattach: function()
    {
        this.update();
    },

    update: function()
    {
        this.nameElement = document.createElement("span");
        this.nameElement.className = "name";
        this.nameElement.textContent = this.property.name;

        var separatorElement = document.createElement("span");
        separatorElement.className = "separator";
        separatorElement.textContent = ": ";
        
        this.valueElement = document.createElement("span");
        this.valueElement.className = "value";
        this.valueElement.textContent = this.property.value.description;
        if (this.property.isGetter)
           this.valueElement.addStyleClass("dimmed");

        this.listItemElement.removeChildren();

        this.listItemElement.appendChild(this.nameElement);
        this.listItemElement.appendChild(separatorElement);
        this.listItemElement.appendChild(this.valueElement);
        this.hasChildren = this.property.value.hasChildren;
    },

    updateSiblings: function()
    {
        if (this.parent.root)
            this.treeOutline.section.update();
        else
            this.parent.shouldRefreshChildren = true;
    },

    startEditing: function()
    {
        if (WebInspector.isBeingEdited(this.valueElement) || !this.treeOutline.section.editable)
            return;

        var context = { expanded: this.expanded };

        // Lie about our children to prevent expanding on double click and to collapse subproperties.
        this.hasChildren = false;

        this.listItemElement.addStyleClass("editing-sub-part");

        WebInspector.startEditing(this.valueElement, this.editingCommitted.bind(this), this.editingCancelled.bind(this), context);
    },

    editingEnded: function(context)
    {
        this.listItemElement.scrollLeft = 0;
        this.listItemElement.removeStyleClass("editing-sub-part");
        if (context.expanded)
            this.expand();
    },

    editingCancelled: function(element, context)
    {
        this.update();
        this.editingEnded(context);
    },

    editingCommitted: function(element, userInput, previousContent, context)
    {
        if (userInput === previousContent)
            return this.editingCancelled(element, context); // nothing changed, so cancel

        this.applyExpression(userInput, true);

        this.editingEnded(context);
    },

    applyExpression: function(expression, updateInterface)
    {
        expression = expression.trimWhitespace();
        var expressionLength = expression.length;
        var self = this;
        var callback = function(success) {
            if (!updateInterface)
                return;

            if (!success)
                self.update();

            if (!expressionLength) {
                // The property was deleted, so remove this tree element.
                self.parent.removeChild(this);
            } else {
                // Call updateSiblings since their value might be based on the value that just changed.
                self.updateSiblings();
            }
        };
        InjectedScriptAccess.setPropertyValue(this.property.parentObjectProxy, this.property.name, expression.trimWhitespace(), callback);
    }
}

WebInspector.ObjectPropertyTreeElement.prototype.__proto__ = TreeElement.prototype;

/* BreakpointsSidebarPane.js */

/*
 * Copyright (C) 2008 Apple Inc. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.BreakpointsSidebarPane = function()
{
    WebInspector.SidebarPane.call(this, WebInspector.UIString("Breakpoints"));

    this.breakpoints = {};

    this.listElement = document.createElement("ol");
    this.listElement.className = "breakpoint-list";

    this.emptyElement = document.createElement("div");
    this.emptyElement.className = "info";
    this.emptyElement.textContent = WebInspector.UIString("No Breakpoints");

    this.bodyElement.appendChild(this.emptyElement);
}

WebInspector.BreakpointsSidebarPane.prototype = {
    addBreakpoint: function(breakpoint)
    {
        if (this.breakpoints[breakpoint.id])
            return;

        this.breakpoints[breakpoint.id] = breakpoint;

        breakpoint.addEventListener("enabled", this._breakpointEnableChanged, this);
        breakpoint.addEventListener("disabled", this._breakpointEnableChanged, this);
        breakpoint.addEventListener("text-changed", this._breakpointTextChanged, this);

        this._appendBreakpointElement(breakpoint);

        if (this.emptyElement.parentElement) {
            this.bodyElement.removeChild(this.emptyElement);
            this.bodyElement.appendChild(this.listElement);
        }

        if (!InspectorController.debuggerEnabled() || !breakpoint.sourceID)
            return;

        if (breakpoint.enabled)
            InspectorController.addBreakpoint(breakpoint.sourceID, breakpoint.line, breakpoint.condition);
    },

    _appendBreakpointElement: function(breakpoint)
    {
        function checkboxClicked(event)
        {
            breakpoint.enabled = !breakpoint.enabled;

            // without this, we'd switch to the source of the clicked breakpoint
            event.stopPropagation();
        }

        function breakpointClicked()
        {
            var script = WebInspector.panels.scripts.scriptOrResourceForID(breakpoint.sourceID);
            if (script)
                WebInspector.panels.scripts.showScript(script, breakpoint.line);
        }

        var breakpointElement = document.createElement("li");
        breakpoint._breakpointListElement = breakpointElement;
        breakpointElement._breakpointObject = breakpoint;
        breakpointElement.addEventListener("click", breakpointClicked, false);

        var checkboxElement = document.createElement("input");
        checkboxElement.className = "checkbox-elem";
        checkboxElement.type = "checkbox";
        checkboxElement.checked = breakpoint.enabled;
        checkboxElement.addEventListener("click", checkboxClicked, false);
        breakpointElement.appendChild(checkboxElement);

        var labelElement = document.createTextNode(breakpoint.label);
        breakpointElement.appendChild(labelElement);

        var sourceTextElement = document.createElement("div");
        sourceTextElement.textContent = breakpoint.sourceText;
        sourceTextElement.className = "source-text";
        breakpointElement.appendChild(sourceTextElement);

        var currentElement = this.listElement.firstChild;
        while (currentElement) {
            var currentBreak = currentElement._breakpointObject;
            if (currentBreak.url > breakpoint.url) {
                this.listElement.insertBefore(breakpointElement, currentElement);
                return;
            } else if (currentBreak.url == breakpoint.url && currentBreak.line > breakpoint.line) {
                this.listElement.insertBefore(breakpointElement, currentElement);
                return;
            }
            currentElement = currentElement.nextSibling;
        }
        this.listElement.appendChild(breakpointElement);
    },

    removeBreakpoint: function(breakpoint)
    {
        if (!this.breakpoints[breakpoint.id])
            return;
        delete this.breakpoints[breakpoint.id];

        breakpoint.removeEventListener("enabled", null, this);
        breakpoint.removeEventListener("disabled", null, this);
        breakpoint.removeEventListener("text-changed", null, this);

        var element = breakpoint._breakpointListElement;
        element.parentElement.removeChild(element);

        if (!this.listElement.firstChild) {
            this.bodyElement.removeChild(this.listElement);
            this.bodyElement.appendChild(this.emptyElement);
        }

        if (!InspectorController.debuggerEnabled() || !breakpoint.sourceID)
            return;

        InspectorController.removeBreakpoint(breakpoint.sourceID, breakpoint.line);
    },

    _breakpointEnableChanged: function(event)
    {
        var breakpoint = event.target;

        var checkbox = breakpoint._breakpointListElement.firstChild;
        checkbox.checked = breakpoint.enabled;

        if (!InspectorController.debuggerEnabled() || !breakpoint.sourceID)
            return;

        if (breakpoint.enabled)
            InspectorController.addBreakpoint(breakpoint.sourceID, breakpoint.line, breakpoint.condition);
        else
            InspectorController.removeBreakpoint(breakpoint.sourceID, breakpoint.line);
    },

    _breakpointTextChanged: function(event)
    {
        var breakpoint = event.target;

        var sourceTextElement = breakpoint._breakpointListElement.firstChild.nextSibling.nextSibling;
        sourceTextElement.textContent = breakpoint.sourceText;
    }
}

WebInspector.BreakpointsSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype;

/* CallStackSidebarPane.js */

/*
 * Copyright (C) 2008 Apple Inc. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.CallStackSidebarPane = function()
{
    WebInspector.SidebarPane.call(this, WebInspector.UIString("Call Stack"));
    
    this._shortcuts = {};

    var shortcut = WebInspector.KeyboardShortcut.makeKey(WebInspector.KeyboardShortcut.KeyCodes.Period,
                                                         WebInspector.KeyboardShortcut.Modifiers.Ctrl);
    this._shortcuts[shortcut] = this._selectNextCallFrameOnStack.bind(this);

    var shortcut = WebInspector.KeyboardShortcut.makeKey(WebInspector.KeyboardShortcut.KeyCodes.Comma,
                                                         WebInspector.KeyboardShortcut.Modifiers.Ctrl);
    this._shortcuts[shortcut] = this._selectPreviousCallFrameOnStack.bind(this);
}

WebInspector.CallStackSidebarPane.prototype = {
    update: function(callFrames, sourceIDMap)
    {
        this.bodyElement.removeChildren();

        this.placards = [];
        delete this._selectedCallFrame;

        if (!callFrames) {
            var infoElement = document.createElement("div");
            infoElement.className = "info";
            infoElement.textContent = WebInspector.UIString("Not Paused");
            this.bodyElement.appendChild(infoElement);
            return;
        }

        var title;
        var subtitle;
        var scriptOrResource;

        for (var i = 0; i < callFrames.length; ++i) {
            var callFrame = callFrames[i];
            switch (callFrame.type) {
            case "function":
                title = callFrame.functionName || WebInspector.UIString("(anonymous function)");
                break;
            case "program":
                title = WebInspector.UIString("(program)");
                break;
            }

            scriptOrResource = sourceIDMap[callFrame.sourceID];
            subtitle = WebInspector.displayNameForURL(scriptOrResource.sourceURL || scriptOrResource.url);

            if (callFrame.line > 0) {
                if (subtitle)
                    subtitle += ":" + callFrame.line;
                else
                    subtitle = WebInspector.UIString("line %d", callFrame.line);
            }

            var placard = new WebInspector.Placard(title, subtitle);
            placard.callFrame = callFrame;

            placard.element.addEventListener("click", this._placardSelected.bind(this), false);

            this.placards.push(placard);
            this.bodyElement.appendChild(placard.element);
        }
    },

    get selectedCallFrame()
    {
        return this._selectedCallFrame;
    },

    set selectedCallFrame(x)
    {
        if (this._selectedCallFrame === x)
            return;

        this._selectedCallFrame = x;

        for (var i = 0; i < this.placards.length; ++i) {
            var placard = this.placards[i];
            placard.selected = (placard.callFrame === this._selectedCallFrame);
        }

        this.dispatchEventToListeners("call frame selected");
    },

    handleKeyEvent: function(event)
    {
        var shortcut = WebInspector.KeyboardShortcut.makeKeyFromEvent(event);
        var handler = this._shortcuts[shortcut];
        if (handler) {
            handler(event);
            event.preventDefault();
            event.handled = true;
        }
    },

    _selectNextCallFrameOnStack: function()
    {
        var index = this._selectedCallFrameIndex();
        if (index == -1)
            return;
        this._selectedPlacardByIndex(index + 1);
    },

    _selectPreviousCallFrameOnStack: function()
    {
        var index = this._selectedCallFrameIndex();
        if (index == -1)
            return;
        this._selectedPlacardByIndex(index - 1);
    },

    _selectedPlacardByIndex: function(index)
    {
        if (index < 0 || index >= this.placards.length)
            return;
        var placard = this.placards[index];
        this.selectedCallFrame = placard.callFrame
    },

    _selectedCallFrameIndex: function()
    {
        if (!this._selectedCallFrame)
            return -1;
        for (var i = 0; i < this.placards.length; ++i) {
            var placard = this.placards[i];
            if (placard.callFrame === this._selectedCallFrame)
                return i;
        }
        return -1;
    },

    _placardSelected: function(event)
    {
        var placardElement = event.target.enclosingNodeOrSelfWithClass("placard");
        this.selectedCallFrame = placardElement.placard.callFrame;
    }
}

WebInspector.CallStackSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype;

/* ScopeChainSidebarPane.js */

/*
 * Copyright (C) 2008 Apple Inc. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.ScopeChainSidebarPane = function()
{
    WebInspector.SidebarPane.call(this, WebInspector.UIString("Scope Variables"));
    this._expandedProperties = [];
}

WebInspector.ScopeChainSidebarPane.prototype = {
    update: function(callFrame)
    {
        this.bodyElement.removeChildren();

        this.sections = [];
        this.callFrame = callFrame;

        if (!callFrame) {
            var infoElement = document.createElement("div");
            infoElement.className = "info";
            infoElement.textContent = WebInspector.UIString("Not Paused");
            this.bodyElement.appendChild(infoElement);
            return;
        }

        var foundLocalScope = false;
        var scopeChain = callFrame.scopeChain;
        for (var i = 0; i < scopeChain.length; ++i) {
            var scopeObjectProxy = scopeChain[i];
            var title = null;
            var subtitle = scopeObjectProxy.description;
            var emptyPlaceholder = null;
            var extraProperties = null;

            if (scopeObjectProxy.isLocal) {
                foundLocalScope = true;
                title = WebInspector.UIString("Local");
                emptyPlaceholder = WebInspector.UIString("No Variables");
                subtitle = null;
                if (scopeObjectProxy.thisObject)
                    extraProperties = [ new WebInspector.ObjectPropertyProxy("this", scopeObjectProxy.thisObject) ];
            } else if (scopeObjectProxy.isClosure) {
                title = WebInspector.UIString("Closure");
                emptyPlaceholder = WebInspector.UIString("No Variables");
                subtitle = null;
            } else if (i === (scopeChain.length - 1))
                title = WebInspector.UIString("Global");
            else if (scopeObjectProxy.isElement)
                title = WebInspector.UIString("Event Target");
            else if (scopeObjectProxy.isDocument)
                title = WebInspector.UIString("Event Document");
            else if (scopeObjectProxy.isWithBlock)
                title = WebInspector.UIString("With Block");

            if (!title || title === subtitle)
                subtitle = null;

            var section = new WebInspector.ObjectPropertiesSection(scopeObjectProxy, title, subtitle, emptyPlaceholder, true, extraProperties, WebInspector.ScopeVariableTreeElement);
            section.editInSelectedCallFrameWhenPaused = true;
            section.pane = this;

            if (!foundLocalScope || scopeObjectProxy.isLocal)
                section.expanded = true;

            this.sections.push(section);
            this.bodyElement.appendChild(section.element);
        }
    }
}

WebInspector.ScopeChainSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype;

WebInspector.ScopeVariableTreeElement = function(property)
{
    WebInspector.ObjectPropertyTreeElement.call(this, property);
}

WebInspector.ScopeVariableTreeElement.prototype = {
    onattach: function()
    {
        WebInspector.ObjectPropertyTreeElement.prototype.onattach.call(this);
        if (this.hasChildren && this.propertyIdentifier in this.treeOutline.section.pane._expandedProperties)
            this.expand();
    },

    onexpand: function()
    {
        this.treeOutline.section.pane._expandedProperties[this.propertyIdentifier] = true;
    },

    oncollapse: function()
    {
        delete this.treeOutline.section.pane._expandedProperties[this.propertyIdentifier];
    },

    get propertyIdentifier()
    {
        if ("_propertyIdentifier" in this)
            return this._propertyIdentifier;
        var section = this.treeOutline.section;
        this._propertyIdentifier = section.title + ":" + (section.subtitle ? section.subtitle + ":" : "") + this.propertyPath;
        return this._propertyIdentifier;
    },

    get propertyPath()
    {
        if ("_propertyPath" in this)
            return this._propertyPath;

        var current = this;
        var result;

        do {
            if (result)
                result = current.property.name + "." + result;
            else
                result = current.property.name;
            current = current.parent;
        } while (current && !current.root);

        this._propertyPath = result;
        return result;
    }
}

WebInspector.ScopeVariableTreeElement.prototype.__proto__ = WebInspector.ObjectPropertyTreeElement.prototype;

/* WatchExpressionsSidebarPane.js */

/*
 * Copyright (C) IBM Corp. 2009  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of IBM Corp. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.WatchExpressionsSidebarPane = function()
{
    WebInspector.SidebarPane.call(this, WebInspector.UIString("Watch Expressions"));

    this.section = new WebInspector.WatchExpressionsSection();

    this.bodyElement.appendChild(this.section.element);

    var addElement = document.createElement("button");
    addElement.setAttribute("type", "button");
    addElement.textContent = WebInspector.UIString("Add");
    addElement.addEventListener("click", this.section.addExpression.bind(this.section), false);

    var refreshElement = document.createElement("button");
    refreshElement.setAttribute("type", "button");
    refreshElement.textContent = WebInspector.UIString("Refresh");
    refreshElement.addEventListener("click", this.section.update.bind(this.section), false);

    var centerElement = document.createElement("div");
    centerElement.addStyleClass("watch-expressions-buttons-container");
    centerElement.appendChild(addElement);
    centerElement.appendChild(refreshElement);
    this.bodyElement.appendChild(centerElement);

    this.expanded = this.section.loadSavedExpressions().length > 0;
    this.onexpand = this.refreshExpressions.bind(this);
}

WebInspector.WatchExpressionsSidebarPane.prototype = {
    refreshExpressions: function()
    {
        this.section.update();
    }
}

WebInspector.WatchExpressionsSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype;

WebInspector.WatchExpressionsSection = function()
{
    WebInspector.ObjectPropertiesSection.call(this);

    this.watchExpressions = this.loadSavedExpressions();

    this.headerElement.className = "hidden";
    this.editable = true;
    this.expanded = true;
    this.propertiesElement.addStyleClass("watch-expressions");

    this._watchObjectGroupId = "watch-group";
}

WebInspector.WatchExpressionsSection.NewWatchExpression = "\xA0";

WebInspector.WatchExpressionsSection.prototype = {
    update: function()
    {
        function appendResult(expression, watchIndex, result, exception)
        {
            if (exception) {
                // Exception results are not wrappers, but text messages.
                result = WebInspector.ObjectProxy.wrapPrimitiveValue(result);
            } else if (result.type === "string") {
                // Evaluation result is intentionally not abbreviated. However, we'd like to distinguish between null and "null"
                result.description = "\"" + result.description + "\"";
            }

            var property = new WebInspector.ObjectPropertyProxy(expression, result);
            property.watchIndex = watchIndex;
            property.isException = exception;

            // For newly added, empty expressions, set description to "",
            // since otherwise you get DOMWindow
            if (property.name === WebInspector.WatchExpressionsSection.NewWatchExpression) 
                property.value.description = "";

            // To clarify what's going on here: 
            // In the outer function, we calculate the number of properties
            // that we're going to be updating, and set that in the
            // propertyCount variable.  
            // In this function, we test to see when we are processing the 
            // last property, and then call the superclass's updateProperties() 
            // method to get all the properties refreshed at once.
            properties.push(property);
            
            if (properties.length == propertyCount) {
                this.updateProperties(properties, WebInspector.WatchExpressionTreeElement, WebInspector.WatchExpressionsSection.CompareProperties);

                // check to see if we just added a new watch expression,
                // which will always be the last property
                if (this._newExpressionAdded) {
                    delete this._newExpressionAdded;

                    treeElement = this.findAddedTreeElement();
                    if (treeElement)
                        treeElement.startEditing();
                }
            }
        }

        InspectorController.releaseWrapperObjectGroup(this._watchObjectGroupId)
        var properties = [];

        // Count the properties, so we known when to call this.updateProperties()
        // in appendResult()
        var propertyCount = 0;
        for (var i = 0; i < this.watchExpressions.length; ++i) {
            if (!this.watchExpressions[i]) 
                continue;
            ++propertyCount;
        }

        // Now process all the expressions, since we have the actual count,
        // which is checked in the appendResult inner function.
        for (var i = 0; i < this.watchExpressions.length; ++i) {
            var expression = this.watchExpressions[i];
            if (!expression)
                continue;

            WebInspector.console.evalInInspectedWindow("(" + expression + ")", this._watchObjectGroupId, appendResult.bind(this, expression, i));
        }

        // note this is setting the expansion of the tree, not the section;
        // with no expressions, and expanded tree, we get some extra vertical
        // white space
        // FIXME: should change to use header buttons instead of the buttons
        // at the bottom of the section, then we can add a "No Watch Expressions
        // element when there are no watch expressions, and this issue should
        // go away.
        this.expanded = (propertyCount != 0);
    },

    addExpression: function()
    {
        this._newExpressionAdded = true;
        this.watchExpressions.push(WebInspector.WatchExpressionsSection.NewWatchExpression);
        this.update();
    },

    updateExpression: function(element, value)
    {
        this.watchExpressions[element.property.watchIndex] = value;
        this.saveExpressions();
        this.update();
    },

    findAddedTreeElement: function()
    {
        var children = this.propertiesTreeOutline.children;
        for (var i = 0; i < children.length; ++i)
            if (children[i].property.name === WebInspector.WatchExpressionsSection.NewWatchExpression)
                return children[i];
    },

    loadSavedExpressions: function()
    {
        var json = InspectorController.setting("watchExpressions");
        if (!json)
            return [];

        try {
            json = JSON.parse(json);
        } catch(e) {
            return [];
        }

        return json.expressions || [];
    },

    saveExpressions: function()
    {
        var toSave = [];
        for (var i = 0; i < this.watchExpressions.length; i++)
            if (this.watchExpressions[i])
                toSave.push(this.watchExpressions[i]);

        var json = JSON.stringify({expressions: toSave});
        InspectorController.setSetting("watchExpressions", json);

        return toSave.length;
    }
}

WebInspector.WatchExpressionsSection.prototype.__proto__ = WebInspector.ObjectPropertiesSection.prototype;

WebInspector.WatchExpressionsSection.CompareProperties = function(propertyA, propertyB) 
{
    if (propertyA.watchIndex == propertyB.watchIndex)
        return 0;
    else if (propertyA.watchIndex < propertyB.watchIndex)
        return -1;
    else
        return 1;
}

WebInspector.WatchExpressionTreeElement = function(property)
{
    WebInspector.ObjectPropertyTreeElement.call(this, property);
}

WebInspector.WatchExpressionTreeElement.prototype = {
    update: function()
    {
        WebInspector.ObjectPropertyTreeElement.prototype.update.call(this);

        if (this.property.isException)
            this.valueElement.addStyleClass("watch-expressions-error-level");

        var deleteButton = document.createElement("input");
        deleteButton.type = "button";
        deleteButton.title = WebInspector.UIString("Delete watch expression.");
        deleteButton.addStyleClass("enabled-button");
        deleteButton.addStyleClass("delete-button");
        deleteButton.addEventListener("click", this._deleteButtonClicked.bind(this), false);

        this.listItemElement.insertBefore(deleteButton, this.listItemElement.firstChild);
    },

    _deleteButtonClicked: function()
    {
        this.treeOutline.section.updateExpression(this, null);
    },

    startEditing: function()
    {
        if (WebInspector.isBeingEdited(this.nameElement) || !this.treeOutline.section.editable)
            return;

        this.nameElement.textContent = this.property.name.trimWhitespace();

        var context = { expanded: this.expanded };

        // collapse temporarily, if required
        this.hasChildren = false;

        this.listItemElement.addStyleClass("editing-sub-part");

        WebInspector.startEditing(this.nameElement, this.editingCommitted.bind(this), this.editingCancelled.bind(this), context);
    },

    editingCancelled: function(element, context)
    {
        if (!this.nameElement.textContent)
            this.treeOutline.section.updateExpression(this, null);
            
        this.update();
        this.editingEnded(context);
    },

    applyExpression: function(expression, updateInterface)
    {
        expression = expression.trimWhitespace();

        if (!expression)
            expression = null;

        this.property.name = expression;
        this.treeOutline.section.updateExpression(this, expression);
    }
}

WebInspector.WatchExpressionTreeElement.prototype.__proto__ = WebInspector.ObjectPropertyTreeElement.prototype;

/* MetricsSidebarPane.js */

/*
 * Copyright (C) 2007 Apple Inc.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.MetricsSidebarPane = function()
{
    WebInspector.SidebarPane.call(this, WebInspector.UIString("Metrics"));
    this._inlineStyleId = null;
}

WebInspector.MetricsSidebarPane.prototype = {
    update: function(node)
    {
        var body = this.bodyElement;

        body.removeChildren();

        if (node)
            this.node = node;
        else
            node = this.node;

        if (!node || !node.ownerDocument.defaultView)
            return;

        if (node.nodeType !== Node.ELEMENT_NODE)
            return;

        var self = this;
        var callback = function(stylePayload) {
            if (!stylePayload)
                return;
            var style = WebInspector.CSSStyleDeclaration.parseStyle(stylePayload);
            self._update(node, body, style);
        };
        InjectedScriptAccess.getComputedStyle(node.id, callback);

        var inlineStyleCallback = function(stylePayload) {
            if (!stylePayload)
                return;
            self._inlineStyleId = stylePayload.id;
        };
        InjectedScriptAccess.getInlineStyle(node.id, inlineStyleCallback);
    },

    _update: function(node, body, style)
    {
        var metricsElement = document.createElement("div");
        metricsElement.className = "metrics";

        function createBoxPartElement(style, name, side, suffix)
        {
            var propertyName = (name !== "position" ? name + "-" : "") + side + suffix;
            var value = style.getPropertyValue(propertyName);
            if (value === "" || (name !== "position" && value === "0px"))
                value = "\u2012";
            else if (name === "position" && value === "auto")
                value = "\u2012";
            value = value.replace(/px$/, "");

            var element = document.createElement("div");
            element.className = side;
            element.textContent = value;
            element.addEventListener("dblclick", this.startEditing.bind(this, element, name, propertyName), false);
            return element;
        }

        // Display types for which margin is ignored.
        var noMarginDisplayType = {
            "table-cell": true,
            "table-column": true,
            "table-column-group": true,
            "table-footer-group": true,
            "table-header-group": true,
            "table-row": true,
            "table-row-group": true
        };

        // Display types for which padding is ignored.
        var noPaddingDisplayType = {
            "table-column": true,
            "table-column-group": true,
            "table-footer-group": true,
            "table-header-group": true,
            "table-row": true,
            "table-row-group": true
        };

        // Position types for which top, left, bottom and right are ignored.
        var noPositionType = {
            "static": true
        };

        var boxes = ["content", "padding", "border", "margin", "position"];
        var boxLabels = [WebInspector.UIString("content"), WebInspector.UIString("padding"), WebInspector.UIString("border"), WebInspector.UIString("margin"), WebInspector.UIString("position")];
        var previousBox;
        for (var i = 0; i < boxes.length; ++i) {
            var name = boxes[i];

            if (name === "margin" && noMarginDisplayType[style.display])
                continue;
            if (name === "padding" && noPaddingDisplayType[style.display])
                continue;
            if (name === "position" && noPositionType[style.position])
                continue;

            var boxElement = document.createElement("div");
            boxElement.className = name;

            if (name === "content") {
                var width = style.width.replace(/px$/, "");
                var widthElement = document.createElement("span");
                widthElement.textContent = width;
                widthElement.addEventListener("dblclick", this.startEditing.bind(this, widthElement, "width", "width"), false);

                var height = style.height.replace(/px$/, "");
                var heightElement = document.createElement("span");
                heightElement.textContent = height;
                heightElement.addEventListener("dblclick", this.startEditing.bind(this, heightElement, "height", "height"), false);

                boxElement.appendChild(widthElement);
                boxElement.appendChild(document.createTextNode(" \u00D7 "));
                boxElement.appendChild(heightElement);
            } else {
                var suffix = (name === "border" ? "-width" : "");

                var labelElement = document.createElement("div");
                labelElement.className = "label";
                labelElement.textContent = boxLabels[i];
                boxElement.appendChild(labelElement);

                boxElement.appendChild(createBoxPartElement.call(this, style, name, "top", suffix));
                boxElement.appendChild(document.createElement("br"));
                boxElement.appendChild(createBoxPartElement.call(this, style, name, "left", suffix));

                if (previousBox)
                    boxElement.appendChild(previousBox);

                boxElement.appendChild(createBoxPartElement.call(this, style, name, "right", suffix));
                boxElement.appendChild(document.createElement("br"));
                boxElement.appendChild(createBoxPartElement.call(this, style, name, "bottom", suffix));
            }

            previousBox = boxElement;
        }

        metricsElement.appendChild(previousBox);
        body.appendChild(metricsElement);
    },

    startEditing: function(targetElement, box, styleProperty)
    {
        if (WebInspector.isBeingEdited(targetElement))
            return;

        var context = { box: box, styleProperty: styleProperty };

        WebInspector.startEditing(targetElement, this.editingCommitted.bind(this), this.editingCancelled.bind(this), context);
    },

    editingCancelled: function(element, context)
    {
        this.update();
    },

    editingCommitted: function(element, userInput, previousContent, context)
    {
        if (userInput === previousContent)
            return this.editingCancelled(element, context); // nothing changed, so cancel

        if (context.box !== "position" && (!userInput || userInput === "\u2012"))
            userInput = "0px";
        else if (context.box === "position" && (!userInput || userInput === "\u2012"))
            userInput = "auto";

        // Append a "px" unit if the user input was just a number.
        if (/^\d+$/.test(userInput))
            userInput += "px";

        var self = this;
        var callback = function(success) {
            if (!success)
                return;
            self.dispatchEventToListeners("metrics edited");
            self.update();
        };
        InjectedScriptAccess.setStyleProperty(this._inlineStyleId, context.styleProperty, userInput, callback);
    }
}

WebInspector.MetricsSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype;

/* PropertiesSidebarPane.js */

/*
 * Copyright (C) 2007 Apple Inc.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.PropertiesSidebarPane = function()
{
    WebInspector.SidebarPane.call(this, WebInspector.UIString("Properties"));
}

WebInspector.PropertiesSidebarPane.prototype = {
    update: function(node)
    {
        var body = this.bodyElement;

        body.removeChildren();

        this.sections = [];

        if (!node)
            return;

        var self = this;
        var callback = function(prototypes) {
            var body = self.bodyElement;
            body.removeChildren();
            self.sections = [];

            // Get array of prototype user-friendly names.
            for (var i = 0; i < prototypes.length; ++i) {
                var prototype = new WebInspector.ObjectProxy(node.id, [], i);
                var section = new WebInspector.ObjectPropertiesSection(prototype, prototypes[i], WebInspector.UIString("Prototype"));
                self.sections.push(section);
                body.appendChild(section.element);
            }
        };
        InjectedScriptAccess.getPrototypes(node.id, callback);
    }
}

WebInspector.PropertiesSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype;

/* EventListenersSidebarPane.js */

/*
 * Copyright (C) 2007 Apple Inc.  All rights reserved.
 * Copyright (C) 2009 Joseph Pecoraro
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.EventListenersSidebarPane = function()
{
    WebInspector.SidebarPane.call(this, WebInspector.UIString("Event Listeners"));
    this.bodyElement.addStyleClass("events-pane");

    this.sections = [];

    this.settingsSelectElement = document.createElement("select");

    var option = document.createElement("option");
    option.value = "all";
    if (Preferences.eventListenersFilter === "all")
        option.selected = true;
    option.label = WebInspector.UIString("All Nodes");
    this.settingsSelectElement.appendChild(option);

    option = document.createElement("option");
    option.value = "selected";
    if (Preferences.eventListenersFilter === "selected")
        option.selected = true;
    option.label = WebInspector.UIString("Selected Node Only");
    this.settingsSelectElement.appendChild(option);

    this.settingsSelectElement.addEventListener("click", function(event) { event.stopPropagation() }, false);
    this.settingsSelectElement.addEventListener("change", this._changeSetting.bind(this), false);

    this.titleElement.appendChild(this.settingsSelectElement);
}

WebInspector.EventListenersSidebarPane.prototype = {
    update: function(node)
    {
        var body = this.bodyElement;
        body.removeChildren();
        this.sections = [];

        var self = this;
        function callback(nodeId, eventListeners) {
            var sectionNames = [];
            var sectionMap = {};
            for (var i = 0; i < eventListeners.length; ++i) {
                var eventListener = eventListeners[i];
                eventListener.node = WebInspector.domAgent.nodeForId(eventListener.nodeId);
                delete eventListener.nodeId; // no longer needed
                if (/^function _inspectorCommandLineAPI_logEvent\(/.test(eventListener.listener.toString()))
                    continue; // ignore event listeners generated by monitorEvent
                var type = eventListener.type;
                var section = sectionMap[type];
                if (!section) {
                    section = new WebInspector.EventListenersSection(type, nodeId);
                    sectionMap[type] = section;
                    sectionNames.push(type);
                    self.sections.push(section);
                }
                section.addListener(eventListener);
            }
            
            if (sectionNames.length === 0) {
                var div = document.createElement("div");
                div.className = "info";
                div.textContent = WebInspector.UIString("No Event Listeners");
                body.appendChild(div);
                return;
            }

            sectionNames.sort();
            for (var i = 0; i < sectionNames.length; ++i) {
                var section = sectionMap[sectionNames[i]];
                section.update();
                body.appendChild(section.element);
            }
        }

        WebInspector.EventListeners.getEventListenersForNodeAsync(node, callback);
    },

    _changeSetting: function(event)
    {
        var selectedOption = this.settingsSelectElement[this.settingsSelectElement.selectedIndex];
        Preferences.eventListenersFilter = selectedOption.value;

        InspectorController.setSetting("event-listeners-filter", Preferences.eventListenersFilter);

        for (var i = 0; i < this.sections.length; ++i)
            this.sections[i].update();
    }
}

WebInspector.EventListenersSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype;

WebInspector.EventListenersSection = function(title, nodeId)
{
    this.eventListeners = [];
    this._nodeId = nodeId;
    WebInspector.PropertiesSection.call(this, title);

    // Changed from a Properties List
    this.propertiesElement.parentNode.removeChild(this.propertiesElement);
    delete this.propertiesElement;
    delete this.propertiesTreeOutline;

    this.eventBars = document.createElement("div");
    this.eventBars.className = "event-bars";
    this.element.appendChild(this.eventBars);
}

WebInspector.EventListenersSection.prototype = {
    update: function()
    {
        // A Filtered Array simplifies when to create connectors
        var filteredEventListeners = this.eventListeners;
        if (Preferences.eventListenersFilter === "selected") {
            filteredEventListeners = [];
            for (var i = 0; i < this.eventListeners.length; ++i) {
                var eventListener = this.eventListeners[i];
                if (eventListener.node.id === this._nodeId)
                    filteredEventListeners.push(eventListener);
            }
        }

        this.eventBars.removeChildren();
        var length = filteredEventListeners.length;
        for (var i = 0; i < length; ++i) {
            var eventListener = filteredEventListeners[i];
            var eventListenerBar = new WebInspector.EventListenerBar(eventListener);
            if (i < length - 1) {
                var connector = document.createElement("div");
                connector.className = "event-bar-connector";
                eventListenerBar.element.appendChild(connector);
            }

            this.eventBars.appendChild(eventListenerBar.element);
        }
    },

    addListener: function(eventListener)
    {
        this.eventListeners.push(eventListener);
    }
}

WebInspector.EventListenersSection.prototype.__proto__ = WebInspector.PropertiesSection.prototype;

WebInspector.EventListenerBar = function(eventListener)
{
    this.eventListener = eventListener;
    WebInspector.ObjectPropertiesSection.call(this, null, this._getFunctionDisplayName(), this._getNodeDisplayName());
    this.editable = false;
    this.element.className = "event-bar"; /* Changed from "section" */
    this.propertiesElement.className = "event-properties"; /* Changed from "properties" */
}

WebInspector.EventListenerBar.prototype = {
    update: function()
    {
        var properties = [];
        for (var propertyName in this.eventListener) {
            // Just build properties in place - no need to reach out for injected script.
            var value = this.eventListener[propertyName];
            if (value instanceof WebInspector.DOMNode)
                value = new WebInspector.ObjectProxy(value.id, [], 0, appropriateSelectorForNode(value), true);
            else
                value = WebInspector.ObjectProxy.wrapPrimitiveValue(value);
            properties.push(new WebInspector.ObjectPropertyProxy(propertyName, value));
        }
        this.updateProperties(properties);
    },

    _getNodeDisplayName: function()
    {
        var node = this.eventListener.node;
        if (!node)
            return "";

        if (node.nodeType === Node.DOCUMENT_NODE)
            return "document";

        return appropriateSelectorForNode(node);
    },

    _getFunctionDisplayName: function()
    {
        // Requires that Function.toString() return at least the function's signature.
        var match = this.eventListener.listener.toString().match(/function ([^\(]+?)\(/);
        return (match ? match[1] : WebInspector.UIString("(anonymous function)"));
    }
}

WebInspector.EventListenerBar.prototype.__proto__ = WebInspector.ObjectPropertiesSection.prototype;

/* Color.js */

/*
 * Copyright (C) 2009 Apple Inc.  All rights reserved.
 * Copyright (C) 2009 Joseph Pecoraro
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.Color = function(str)
{
    this.value = str;
    this._parse();
}

WebInspector.Color.prototype = {
    get shorthex()
    {
        if ("_short" in this)
            return this._short;

        if (!this.simple)
            return null;

        var hex = this.hex;
        if (hex.charAt(0) === hex.charAt(1) && hex.charAt(2) === hex.charAt(3) && hex.charAt(4) === hex.charAt(5))
            this._short = hex.charAt(0) + hex.charAt(2) + hex.charAt(4);
        else
            this._short = hex;

        return this._short;
    },

    get hex()
    {
        if (!this.simple)
            return null;

        return this._hex;
    },

    set hex(x)
    {
        this._hex = x;
    },

    get rgb()
    {
        if ("_rgb" in this)
            return this._rgb;

        if (this.simple)
            this._rgb = this._hexToRGB(this.hex);
        else {
            var rgba = this.rgba;
            this._rgb = [rgba[0], rgba[1], rgba[2]];
        }

        return this._rgb;
    },

    set rgb(x)
    {
        this._rgb = x;
    },

    get hsl()
    {
        if ("_hsl" in this)
            return this._hsl;

        this._hsl = this._rgbToHSL(this.rgb);
        return this._hsl;
    },

    set hsl(x)
    {
        this._hsl = x;
    },

    get nickname()
    {
        if (typeof this._nickname !== "undefined") // would be set on parse if there was a nickname
            return this._nickname;
        else
            return null;
    },

    set nickname(x)
    {
        this._nickname = x;
    },

    get rgba()
    {
        return this._rgba;
    },

    set rgba(x)
    {
        this._rgba = x;
    },

    get hsla()
    {
        return this._hsla;
    },

    set hsla(x)
    {
        this._hsla = x;
    },

    hasShortHex: function()
    {
        var shorthex = this.shorthex;
        return (shorthex && shorthex.length === 3);
    },

    toString: function(format)
    {
        if (!format)
            format = this.format;

        switch (format) {
            case "rgb":
                return "rgb(" + this.rgb.join(", ") + ")";
            case "rgba":
                return "rgba(" + this.rgba.join(", ") + ")";
            case "hsl":
                var hsl = this.hsl;
                return "hsl(" + hsl[0] + ", " + hsl[1] + "%, " + hsl[2] + "%)";
            case "hsla":
                var hsla = this.hsla;
                return "hsla(" + hsla[0] + ", " + hsla[1] + "%, " + hsla[2] + "%, " + hsla[3] + ")";
            case "hex":
                return "#" + this.hex;
            case "shorthex":
                return "#" + this.shorthex;
            case "nickname":
                return this.nickname;
        }

        throw "invalid color format";
    },

    _rgbToHex: function(rgb)
    {
        var r = parseInt(rgb[0]).toString(16);
        var g = parseInt(rgb[1]).toString(16);
        var b = parseInt(rgb[2]).toString(16);
        if (r.length === 1)
            r = "0" + r;
        if (g.length === 1)
            g = "0" + g;
        if (b.length === 1)
            b = "0" + b;

        return (r + g + b).toUpperCase();
    },

    _hexToRGB: function(hex)
    {
        var r = parseInt(hex.substring(0,2), 16);
        var g = parseInt(hex.substring(2,4), 16);
        var b = parseInt(hex.substring(4,6), 16);

        return [r, g, b];
    },

    _rgbToHSL: function(rgb)
    {
        var r = parseInt(rgb[0]) / 255;
        var g = parseInt(rgb[1]) / 255;
        var b = parseInt(rgb[2]) / 255;
        var max = Math.max(r, g, b);
        var min = Math.min(r, g, b);
        var diff = max - min;
        var add = max + min;

        if (min === max)
            var h = 0;
        else if (r === max)
            var h = ((60 * (g - b) / diff) + 360) % 360;
        else if (g === max)
            var h = (60 * (b - r) / diff) + 120;
        else
            var h = (60 * (r - g) / diff) + 240;

        var l = 0.5 * add;

        if (l === 0)
            var s = 0;
        else if (l === 1)
            var s = 1;
        else if (l <= 0.5)
            var s = diff / add;
        else
            var s = diff / (2 - add);

        h = Math.round(h);
        s = Math.round(s*100);
        l = Math.round(l*100);

        return [h, s, l];
    },

    _hslToRGB: function(hsl)
    {
        var h = parseFloat(hsl[0]) / 360;
        var s = parseFloat(hsl[1]) / 100;
        var l = parseFloat(hsl[2]) / 100;

        if (l <= 0.5)
            var q = l * (1 + s);
        else
            var q = l + s - (l * s);

        var p = 2 * l - q;

        var tr = h + (1 / 3);
        var tg = h;
        var tb = h - (1 / 3);

        var r = Math.round(hueToRGB(p, q, tr) * 255);
        var g = Math.round(hueToRGB(p, q, tg) * 255);
        var b = Math.round(hueToRGB(p, q, tb) * 255);
        return [r, g, b];

        function hueToRGB(p, q, h) {
            if (h < 0)
                h += 1;
            else if (h > 1)
                h -= 1;

            if ((h * 6) < 1)
                return p + (q - p) * h * 6;
            else if ((h * 2) < 1)
                return q;
            else if ((h * 3) < 2)
                return p + (q - p) * ((2 / 3) - h) * 6;
            else
                return p;
        }
    },

    _rgbaToHSLA: function(rgba)
    {
        var alpha = rgba[3];
        var hsl = this._rgbToHSL(rgba)
        hsl.push(alpha);
        return hsl;
    },

    _hslaToRGBA: function(hsla)
    {
        var alpha = hsla[3];
        var rgb = this._hslToRGB(hsla);
        rgb.push(alpha);
        return rgb;
    },

    _parse: function()
    {
        // Special Values - Advanced but Must Be Parsed First - transparent
        var value = this.value.toLowerCase().replace(/%|\s+/g, "");
        if (value in WebInspector.Color.AdvancedNickNames) {
            this.format = "nickname";
            var set = WebInspector.Color.AdvancedNickNames[value];
            this.simple = false;
            this.rgba = set[0];
            this.hsla = set[1];
            this.nickname = set[2];
            this.alpha = set[0][3];
            return;
        }

        // Simple - #hex, rgb(), nickname, hsl()
        var simple = /^(?:#([0-9a-f]{3,6})|rgb\(([^)]+)\)|(\w+)|hsl\(([^)]+)\))$/i;
        var match = this.value.match(simple);
        if (match) {
            this.simple = true;

            if (match[1]) { // hex
                var hex = match[1].toUpperCase();
                if (hex.length === 3) {
                    this.format = "shorthex";
                    this.hex = hex.charAt(0) + hex.charAt(0) + hex.charAt(1) + hex.charAt(1) + hex.charAt(2) + hex.charAt(2);
                } else {
                    this.format = "hex";
                    this.hex = hex;
                }
            } else if (match[2]) { // rgb
                this.format = "rgb";
                var rgb = match[2].split(/\s*,\s*/);
                this.rgb = rgb;
                this.hex = this._rgbToHex(rgb);
            } else if (match[3]) { // nickname
                var nickname = match[3].toLowerCase();
                if (nickname in WebInspector.Color.Nicknames) {
                    this.format = "nickname";
                    this.hex = WebInspector.Color.Nicknames[nickname];
                } else // unknown name
                    throw "unknown color name";
            } else if (match[4]) { // hsl
                this.format = "hsl";
                var hsl = match[4].replace(/%g/, "").split(/\s*,\s*/);
                this.hsl = hsl;
                this.rgb = this._hslToRGB(hsl);
                this.hex = this._rgbToHex(this.rgb);
            }

            // Fill in the values if this is a known hex color
            var hex = this.hex;
            if (hex && hex in WebInspector.Color.HexTable) {
                var set = WebInspector.Color.HexTable[hex];
                this.rgb = set[0];
                this.hsl = set[1];
                this.nickname = set[2];
            }

            return;
        }

        // Advanced - rgba(), hsla()
        var advanced = /^(?:rgba\(([^)]+)\)|hsla\(([^)]+)\))$/;
        match = this.value.match(advanced);
        if (match) {
            this.simple = false;
            if (match[1]) { // rgba
                this.format = "rgba";
                this.rgba = match[1].split(/\s*,\s*/);
                this.hsla = this._rgbaToHSLA(this.rgba);
                this.alpha = this.rgba[3];
            } else if (match[2]) { // hsla
                this.format = "hsla";
                this.hsla = match[2].replace(/%/g, "").split(/\s*,\s*/);
                this.rgba = this._hslaToRGBA(this.hsla);
                this.alpha = this.hsla[3];
            }

            return;
        }

        // Could not parse as a valid color
        throw "could not parse color";
    }
}

// Simple Values: [rgb, hsl, nickname]
WebInspector.Color.HexTable = {
    "000000": [[0, 0, 0], [0, 0, 0], "black"],
    "000080": [[0, 0, 128], [240, 100, 25], "navy"],
    "00008B": [[0, 0, 139], [240, 100, 27], "darkBlue"],
    "0000CD": [[0, 0, 205], [240, 100, 40], "mediumBlue"],
    "0000FF": [[0, 0, 255], [240, 100, 50], "blue"],
    "006400": [[0, 100, 0], [120, 100, 20], "darkGreen"],
    "008000": [[0, 128, 0], [120, 100, 25], "green"],
    "008080": [[0, 128, 128], [180, 100, 25], "teal"],
    "008B8B": [[0, 139, 139], [180, 100, 27], "darkCyan"],
    "00BFFF": [[0, 191, 255], [195, 100, 50], "deepSkyBlue"],
    "00CED1": [[0, 206, 209], [181, 100, 41], "darkTurquoise"],
    "00FA9A": [[0, 250, 154], [157, 100, 49], "mediumSpringGreen"],
    "00FF00": [[0, 255, 0], [120, 100, 50], "lime"],
    "00FF7F": [[0, 255, 127], [150, 100, 50], "springGreen"],
    "00FFFF": [[0, 255, 255], [180, 100, 50], "cyan"],
    "191970": [[25, 25, 112], [240, 64, 27], "midnightBlue"],
    "1E90FF": [[30, 144, 255], [210, 100, 56], "dodgerBlue"],
    "20B2AA": [[32, 178, 170], [177, 70, 41], "lightSeaGreen"],
    "228B22": [[34, 139, 34], [120, 61, 34], "forestGreen"],
    "2E8B57": [[46, 139, 87], [146, 50, 36], "seaGreen"],
    "2F4F4F": [[47, 79, 79], [180, 25, 25], "darkSlateGray"],
    "32CD32": [[50, 205, 50], [120, 61, 50], "limeGreen"],
    "3CB371": [[60, 179, 113], [147, 50, 47], "mediumSeaGreen"],
    "40E0D0": [[64, 224, 208], [174, 72, 56], "turquoise"],
    "4169E1": [[65, 105, 225], [225, 73, 57], "royalBlue"],
    "4682B4": [[70, 130, 180], [207, 44, 49], "steelBlue"],
    "483D8B": [[72, 61, 139], [248, 39, 39], "darkSlateBlue"],
    "48D1CC": [[72, 209, 204], [178, 60, 55], "mediumTurquoise"],
    "4B0082": [[75, 0, 130], [275, 100, 25], "indigo"],
    "556B2F": [[85, 107, 47], [82, 39, 30], "darkOliveGreen"],
    "5F9EA0": [[95, 158, 160], [182, 25, 50], "cadetBlue"],
    "6495ED": [[100, 149, 237], [219, 79, 66], "cornflowerBlue"],
    "66CDAA": [[102, 205, 170], [160, 51, 60], "mediumAquaMarine"],
    "696969": [[105, 105, 105], [0, 0, 41], "dimGray"],
    "6A5ACD": [[106, 90, 205], [248, 53, 58], "slateBlue"],
    "6B8E23": [[107, 142, 35], [80, 60, 35], "oliveDrab"],
    "708090": [[112, 128, 144], [210, 13, 50], "slateGray"],
    "778899": [[119, 136, 153], [210, 14, 53], "lightSlateGray"],
    "7B68EE": [[123, 104, 238], [249, 80, 67], "mediumSlateBlue"],
    "7CFC00": [[124, 252, 0], [90, 100, 49], "lawnGreen"],
    "7FFF00": [[127, 255, 0], [90, 100, 50], "chartreuse"],
    "7FFFD4": [[127, 255, 212], [160, 100, 75], "aquamarine"],
    "800000": [[128, 0, 0], [0, 100, 25], "maroon"],
    "800080": [[128, 0, 128], [300, 100, 25], "purple"],
    "808000": [[128, 128, 0], [60, 100, 25], "olive"],
    "808080": [[128, 128, 128], [0, 0, 50], "gray"],
    "87CEEB": [[135, 206, 235], [197, 71, 73], "skyBlue"],
    "87CEFA": [[135, 206, 250], [203, 92, 75], "lightSkyBlue"],
    "8A2BE2": [[138, 43, 226], [271, 76, 53], "blueViolet"],
    "8B0000": [[139, 0, 0], [0, 100, 27], "darkRed"],
    "8B008B": [[139, 0, 139], [300, 100, 27], "darkMagenta"],
    "8B4513": [[139, 69, 19], [25, 76, 31], "saddleBrown"],
    "8FBC8F": [[143, 188, 143], [120, 25, 65], "darkSeaGreen"],
    "90EE90": [[144, 238, 144], [120, 73, 75], "lightGreen"],
    "9370D8": [[147, 112, 219], [260, 60, 65], "mediumPurple"],
    "9400D3": [[148, 0, 211], [282, 100, 41], "darkViolet"],
    "98FB98": [[152, 251, 152], [120, 93, 79], "paleGreen"],
    "9932CC": [[153, 50, 204], [280, 61, 50], "darkOrchid"],
    "9ACD32": [[154, 205, 50], [80, 61, 50], "yellowGreen"],
    "A0522D": [[160, 82, 45], [19, 56, 40], "sienna"],
    "A52A2A": [[165, 42, 42], [0, 59, 41], "brown"],
    "A9A9A9": [[169, 169, 169], [0, 0, 66], "darkGray"],
    "ADD8E6": [[173, 216, 230], [195, 53, 79], "lightBlue"],
    "ADFF2F": [[173, 255, 47], [84, 100, 59], "greenYellow"],
    "AFEEEE": [[175, 238, 238], [180, 65, 81], "paleTurquoise"],
    "B0C4DE": [[176, 196, 222], [214, 41, 78], "lightSteelBlue"],
    "B0E0E6": [[176, 224, 230], [187, 52, 80], "powderBlue"],
    "B22222": [[178, 34, 34], [0, 68, 42], "fireBrick"],
    "B8860B": [[184, 134, 11], [43, 89, 38], "darkGoldenrod"],
    "BA55D3": [[186, 85, 211], [288, 59, 58], "mediumOrchid"],
    "BC8F8F": [[188, 143, 143], [0, 25, 65], "rosyBrown"],
    "BDB76B": [[189, 183, 107], [56, 38, 58], "darkKhaki"],
    "C0C0C0": [[192, 192, 192], [0, 0, 75], "silver"],
    "C71585": [[199, 21, 133], [322, 81, 43], "mediumVioletRed"],
    "CD5C5C": [[205, 92, 92], [0, 53, 58], "indianRed"],
    "CD853F": [[205, 133, 63], [30, 59, 53], "peru"],
    "D2691E": [[210, 105, 30], [25, 75, 47], "chocolate"],
    "D2B48C": [[210, 180, 140], [34, 44, 69], "tan"],
    "D3D3D3": [[211, 211, 211], [0, 0, 83], "lightGrey"],
    "D87093": [[219, 112, 147], [340, 60, 65], "paleVioletRed"],
    "D8BFD8": [[216, 191, 216], [300, 24, 80], "thistle"],
    "DA70D6": [[218, 112, 214], [302, 59, 65], "orchid"],
    "DAA520": [[218, 165, 32], [43, 74, 49], "goldenrod"],
    "DC143C": [[237, 164, 61], [35, 83, 58], "crimson"],
    "DCDCDC": [[220, 220, 220], [0, 0, 86], "gainsboro"],
    "DDA0DD": [[221, 160, 221], [300, 47, 75], "plum"],
    "DEB887": [[222, 184, 135], [34, 57, 70], "burlyWood"],
    "E0FFFF": [[224, 255, 255], [180, 100, 94], "lightCyan"],
    "E6E6FA": [[230, 230, 250], [240, 67, 94], "lavender"],
    "E9967A": [[233, 150, 122], [15, 72, 70], "darkSalmon"],
    "EE82EE": [[238, 130, 238], [300, 76, 72], "violet"],
    "EEE8AA": [[238, 232, 170], [55, 67, 80], "paleGoldenrod"],
    "F08080": [[240, 128, 128], [0, 79, 72], "lightCoral"],
    "F0E68C": [[240, 230, 140], [54, 77, 75], "khaki"],
    "F0F8FF": [[240, 248, 255], [208, 100, 97], "aliceBlue"],
    "F0FFF0": [[240, 255, 240], [120, 100, 97], "honeyDew"],
    "F0FFFF": [[240, 255, 255], [180, 100, 97], "azure"],
    "F4A460": [[244, 164, 96], [28, 87, 67], "sandyBrown"],
    "F5DEB3": [[245, 222, 179], [39, 77, 83], "wheat"],
    "F5F5DC": [[245, 245, 220], [60, 56, 91], "beige"],
    "F5F5F5": [[245, 245, 245], [0, 0, 96], "whiteSmoke"],
    "F5FFFA": [[245, 255, 250], [150, 100, 98], "mintCream"],
    "F8F8FF": [[248, 248, 255], [240, 100, 99], "ghostWhite"],
    "FA8072": [[250, 128, 114], [6, 93, 71], "salmon"],
    "FAEBD7": [[250, 235, 215], [34, 78, 91], "antiqueWhite"],
    "FAF0E6": [[250, 240, 230], [30, 67, 94], "linen"],
    "FAFAD2": [[250, 250, 210], [60, 80, 90], "lightGoldenrodYellow"],
    "FDF5E6": [[253, 245, 230], [39, 85, 95], "oldLace"],
    "FF0000": [[255, 0, 0], [0, 100, 50], "red"],
    "FF00FF": [[255, 0, 255], [300, 100, 50], "magenta"],
    "FF1493": [[255, 20, 147], [328, 100, 54], "deepPink"],
    "FF4500": [[255, 69, 0], [16, 100, 50], "orangeRed"],
    "FF6347": [[255, 99, 71], [9, 100, 64], "tomato"],
    "FF69B4": [[255, 105, 180], [330, 100, 71], "hotPink"],
    "FF7F50": [[255, 127, 80], [16, 100, 66], "coral"],
    "FF8C00": [[255, 140, 0], [33, 100, 50], "darkOrange"],
    "FFA07A": [[255, 160, 122], [17, 100, 74], "lightSalmon"],
    "FFA500": [[255, 165, 0], [39, 100, 50], "orange"],
    "FFB6C1": [[255, 182, 193], [351, 100, 86], "lightPink"],
    "FFC0CB": [[255, 192, 203], [350, 100, 88], "pink"],
    "FFD700": [[255, 215, 0], [51, 100, 50], "gold"],
    "FFDAB9": [[255, 218, 185], [28, 100, 86], "peachPuff"],
    "FFDEAD": [[255, 222, 173], [36, 100, 84], "navajoWhite"],
    "FFE4B5": [[255, 228, 181], [38, 100, 85], "moccasin"],
    "FFE4C4": [[255, 228, 196], [33, 100, 88], "bisque"],
    "FFE4E1": [[255, 228, 225], [6, 100, 94], "mistyRose"],
    "FFEBCD": [[255, 235, 205], [36, 100, 90], "blanchedAlmond"],
    "FFEFD5": [[255, 239, 213], [37, 100, 92], "papayaWhip"],
    "FFF0F5": [[255, 240, 245], [340, 100, 97], "lavenderBlush"],
    "FFF5EE": [[255, 245, 238], [25, 100, 97], "seaShell"],
    "FFF8DC": [[255, 248, 220], [48, 100, 93], "cornsilk"],
    "FFFACD": [[255, 250, 205], [54, 100, 90], "lemonChiffon"],
    "FFFAF0": [[255, 250, 240], [40, 100, 97], "floralWhite"],
    "FFFAFA": [[255, 250, 250], [0, 100, 99], "snow"],
    "FFFF00": [[255, 255, 0], [60, 100, 50], "yellow"],
    "FFFFE0": [[255, 255, 224], [60, 100, 94], "lightYellow"],
    "FFFFF0": [[255, 255, 240], [60, 100, 97], "ivory"],
    "FFFFFF": [[255, 255, 255], [0, 100, 100], "white"]
};

// Simple Values
WebInspector.Color.Nicknames = {
    "aliceblue": "F0F8FF",
    "antiquewhite": "FAEBD7",
    "aqua": "00FFFF",
    "aquamarine": "7FFFD4",
    "azure": "F0FFFF",
    "beige": "F5F5DC",
    "bisque": "FFE4C4",
    "black": "000000",
    "blanchedalmond": "FFEBCD",
    "blue": "0000FF",
    "blueviolet": "8A2BE2",
    "brown": "A52A2A",
    "burlywood": "DEB887",
    "cadetblue": "5F9EA0",
    "chartreuse": "7FFF00",
    "chocolate": "D2691E",
    "coral": "FF7F50",
    "cornflowerblue": "6495ED",
    "cornsilk": "FFF8DC",
    "crimson": "DC143C",
    "cyan": "00FFFF",
    "darkblue": "00008B",
    "darkcyan": "008B8B",
    "darkgoldenrod": "B8860B",
    "darkgray": "A9A9A9",
    "darkgreen": "006400",
    "darkkhaki": "BDB76B",
    "darkmagenta": "8B008B",
    "darkolivegreen": "556B2F",
    "darkorange": "FF8C00",
    "darkorchid": "9932CC",
    "darkred": "8B0000",
    "darksalmon": "E9967A",
    "darkseagreen": "8FBC8F",
    "darkslateblue": "483D8B",
    "darkslategray": "2F4F4F",
    "darkturquoise": "00CED1",
    "darkviolet": "9400D3",
    "deeppink": "FF1493",
    "deepskyblue": "00BFFF",
    "dimgray": "696969",
    "dodgerblue": "1E90FF",
    "firebrick": "B22222",
    "floralwhite": "FFFAF0",
    "forestgreen": "228B22",
    "fuchsia": "FF00FF",
    "gainsboro": "DCDCDC",
    "ghostwhite": "F8F8FF",
    "gold": "FFD700",
    "goldenrod": "DAA520",
    "gray": "808080",
    "green": "008000",
    "greenyellow": "ADFF2F",
    "honeydew": "F0FFF0",
    "hotpink": "FF69B4",
    "indianred": "CD5C5C",
    "indigo": "4B0082",
    "ivory": "FFFFF0",
    "khaki": "F0E68C",
    "lavender": "E6E6FA",
    "lavenderblush": "FFF0F5",
    "lawngreen": "7CFC00",
    "lemonchiffon": "FFFACD",
    "lightblue": "ADD8E6",
    "lightcoral": "F08080",
    "lightcyan": "E0FFFF",
    "lightgoldenrodyellow": "FAFAD2",
    "lightgreen": "90EE90",
    "lightgrey": "D3D3D3",
    "lightpink": "FFB6C1",
    "lightsalmon": "FFA07A",
    "lightseagreen": "20B2AA",
    "lightskyblue": "87CEFA",
    "lightslategray": "778899",
    "lightsteelblue": "B0C4DE",
    "lightyellow": "FFFFE0",
    "lime": "00FF00",
    "limegreen": "32CD32",
    "linen": "FAF0E6",
    "magenta": "FF00FF",
    "maroon": "800000",
    "mediumaquamarine": "66CDAA",
    "mediumblue": "0000CD",
    "mediumorchid": "BA55D3",
    "mediumpurple": "9370D8",
    "mediumseagreen": "3CB371",
    "mediumslateblue": "7B68EE",
    "mediumspringgreen": "00FA9A",
    "mediumturquoise": "48D1CC",
    "mediumvioletred": "C71585",
    "midnightblue": "191970",
    "mintcream": "F5FFFA",
    "mistyrose": "FFE4E1",
    "moccasin": "FFE4B5",
    "navajowhite": "FFDEAD",
    "navy": "000080",
    "oldlace": "FDF5E6",
    "olive": "808000",
    "olivedrab": "6B8E23",
    "orange": "FFA500",
    "orangered": "FF4500",
    "orchid": "DA70D6",
    "palegoldenrod": "EEE8AA",
    "palegreen": "98FB98",
    "paleturquoise": "AFEEEE",
    "palevioletred": "D87093",
    "papayawhip": "FFEFD5",
    "peachpuff": "FFDAB9",
    "peru": "CD853F",
    "pink": "FFC0CB",
    "plum": "DDA0DD",
    "powderblue": "B0E0E6",
    "purple": "800080",
    "red": "FF0000",
    "rosybrown": "BC8F8F",
    "royalblue": "4169E1",
    "saddlebrown": "8B4513",
    "salmon": "FA8072",
    "sandybrown": "F4A460",
    "seagreen": "2E8B57",
    "seashell": "FFF5EE",
    "sienna": "A0522D",
    "silver": "C0C0C0",
    "skyblue": "87CEEB",
    "slateblue": "6A5ACD",
    "slategray": "708090",
    "snow": "FFFAFA",
    "springgreen": "00FF7F",
    "steelblue": "4682B4",
    "tan": "D2B48C",
    "teal": "008080",
    "thistle": "D8BFD8",
    "tomato": "FF6347",
    "turquoise": "40E0D0",
    "violet": "EE82EE",
    "wheat": "F5DEB3",
    "white": "FFFFFF",
    "whitesmoke": "F5F5F5",
    "yellow": "FFFF00",
    "yellowgreen": "9ACD32"
};

// Advanced Values [rgba, hsla, nickname]
WebInspector.Color.AdvancedNickNames = {
    "transparent": [[0, 0, 0, 0], [0, 0, 0, 0], "transparent"],
    "rgba(0,0,0,0)": [[0, 0, 0, 0], [0, 0, 0, 0], "transparent"],
    "hsla(0,0,0,0)": [[0, 0, 0, 0], [0, 0, 0, 0], "transparent"],
};

/* StylesSidebarPane.js */

/*
 * Copyright (C) 2007 Apple Inc.  All rights reserved.
 * Copyright (C) 2009 Joseph Pecoraro
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.StylesSidebarPane = function()
{
    WebInspector.SidebarPane.call(this, WebInspector.UIString("Styles"));

    this.settingsSelectElement = document.createElement("select");

    var option = document.createElement("option");
    option.value = "hex";
    option.action = this._changeColorFormat.bind(this);
    if (Preferences.colorFormat === "hex")
        option.selected = true;
    option.label = WebInspector.UIString("Hex Colors");
    this.settingsSelectElement.appendChild(option);

    option = document.createElement("option");
    option.value = "rgb";
    option.action = this._changeColorFormat.bind(this);
    if (Preferences.colorFormat === "rgb")
        option.selected = true;
    option.label = WebInspector.UIString("RGB Colors");
    this.settingsSelectElement.appendChild(option);

    option = document.createElement("option");
    option.value = "hsl";
    option.action = this._changeColorFormat.bind(this);
    if (Preferences.colorFormat === "hsl")
        option.selected = true;
    option.label = WebInspector.UIString("HSL Colors");
    this.settingsSelectElement.appendChild(option);

    this.settingsSelectElement.appendChild(document.createElement("hr"));

    option = document.createElement("option");
    option.action = this._createNewRule.bind(this);
    option.label = WebInspector.UIString("New Style Rule");
    this.settingsSelectElement.appendChild(option);

    this.settingsSelectElement.addEventListener("click", function(event) { event.stopPropagation() }, false);
    this.settingsSelectElement.addEventListener("change", this._changeSetting.bind(this), false);

    this.titleElement.appendChild(this.settingsSelectElement);
}

WebInspector.StylesSidebarPane.prototype = {
    update: function(node, editedSection, forceUpdate)
    {
        var refresh = false;

        if (forceUpdate)
            delete this.node;

        if (!forceUpdate && (!node || node === this.node))
            refresh = true;

        if (node && node.nodeType === Node.TEXT_NODE && node.parentNode)
            node = node.parentNode;

        if (node && node.nodeType !== Node.ELEMENT_NODE)
            node = null;

        if (node)
            this.node = node;
        else
            node = this.node;

        var body = this.bodyElement;

        if (!node) {
            body.removeChildren();
            this.sections = [];
            return;
        }

        var self = this;
        function callback(styles)
        {
            if (!styles)
                return;
            node._setStyles(styles.computedStyle, styles.inlineStyle, styles.styleAttributes, styles.matchedCSSRules);
            self._update(refresh, body, node, editedSection, forceUpdate);
        }

        InjectedScriptAccess.getStyles(node.id, !Preferences.showUserAgentStyles, callback);
    },

    _update: function(refresh, body, node, editedSection, forceUpdate)
    {
        if (!refresh) {
            body.removeChildren();
            this.sections = [];
        }

        var styleRules = [];

        if (refresh) {
            for (var i = 0; i < this.sections.length; ++i) {
                var section = this.sections[i];
                if (section instanceof WebInspector.BlankStylePropertiesSection)
                    continue;
                if (section.computedStyle)
                    section.styleRule.style = node.ownerDocument.defaultView.getComputedStyle(node);
                var styleRule = { section: section, style: section.styleRule.style, computedStyle: section.computedStyle, rule: section.rule };
                styleRules.push(styleRule);
            }
        } else {
            var computedStyle = node.ownerDocument.defaultView.getComputedStyle(node);
            styleRules.push({ computedStyle: true, selectorText: WebInspector.UIString("Computed Style"), style: computedStyle, editable: false });

            var nodeName = node.nodeName.toLowerCase();
            for (var i = 0; i < node.attributes.length; ++i) {
                var attr = node.attributes[i];
                if (attr.style) {
                    var attrStyle = { style: attr.style, editable: false };
                    attrStyle.subtitle = WebInspector.UIString("element’s “%s” attribute", attr.name);
                    attrStyle.selectorText = nodeName + "[" + attr.name;
                    if (attr.value.length)
                        attrStyle.selectorText += "=" + attr.value;
                    attrStyle.selectorText += "]";
                    styleRules.push(attrStyle);
                }
            }

            // Always Show element's Style Attributes
            if (node.nodeType === Node.ELEMENT_NODE) {
                var inlineStyle = { selectorText: WebInspector.UIString("Style Attribute"), style: node.style, isAttribute: true };
                inlineStyle.subtitle = WebInspector.UIString("element’s “%s” attribute", "style");
                styleRules.push(inlineStyle);
            }

            var matchedStyleRules = node.ownerDocument.defaultView.getMatchedCSSRules(node, "", !Preferences.showUserAgentStyles);
            if (matchedStyleRules) {
                // Add rules in reverse order to match the cascade order.
                for (var i = (matchedStyleRules.length - 1); i >= 0; --i) {
                    var rule = matchedStyleRules[i];
                    styleRules.push({ style: rule.style, selectorText: rule.selectorText, parentStyleSheet: rule.parentStyleSheet, rule: rule });
                }
            }
        }

        function deleteDisabledProperty(style, name)
        {
            if (!style || !name)
                return;
            if (style.__disabledPropertyValues)
                delete style.__disabledPropertyValues[name];
            if (style.__disabledPropertyPriorities)
                delete style.__disabledPropertyPriorities[name];
            if (style.__disabledProperties)
                delete style.__disabledProperties[name];
        }

        var usedProperties = {};
        var disabledComputedProperties = {};
        var priorityUsed = false;

        // Walk the style rules and make a list of all used and overloaded properties.
        for (var i = 0; i < styleRules.length; ++i) {
            var styleRule = styleRules[i];
            if (styleRule.computedStyle)
                continue;
            if (styleRule.section && styleRule.section.noAffect)
                continue;

            styleRule.usedProperties = {};

            var style = styleRule.style;
            for (var j = 0; j < style.length; ++j) {
                var name = style[j];

                if (!priorityUsed && style.getPropertyPriority(name).length)
                    priorityUsed = true;

                // If the property name is already used by another rule then this rule's
                // property is overloaded, so don't add it to the rule's usedProperties.
                if (!(name in usedProperties))
                    styleRule.usedProperties[name] = true;

                if (name === "font") {
                    // The font property is not reported as a shorthand. Report finding the individual
                    // properties so they are visible in computed style.
                    // FIXME: remove this when http://bugs.webkit.org/show_bug.cgi?id=15598 is fixed.
                    styleRule.usedProperties["font-family"] = true;
                    styleRule.usedProperties["font-size"] = true;
                    styleRule.usedProperties["font-style"] = true;
                    styleRule.usedProperties["font-variant"] = true;
                    styleRule.usedProperties["font-weight"] = true;
                    styleRule.usedProperties["line-height"] = true;
                }

                // Delete any disabled properties, since the property does exist.
                // This prevents it from showing twice.
                deleteDisabledProperty(style, name);
                deleteDisabledProperty(style, style.getPropertyShorthand(name));
            }

            // Add all the properties found in this style to the used properties list.
            // Do this here so only future rules are affect by properties used in this rule.
            for (var name in styleRules[i].usedProperties)
                usedProperties[name] = true;

            // Remember all disabled properties so they show up in computed style.
            if (style.__disabledProperties)
                for (var name in style.__disabledProperties)
                    disabledComputedProperties[name] = true;
        }

        if (priorityUsed) {
            // Walk the properties again and account for !important.
            var foundPriorityProperties = [];

            // Walk in reverse to match the order !important overrides.
            for (var i = (styleRules.length - 1); i >= 0; --i) {
                if (styleRules[i].computedStyle)
                    continue;

                var style = styleRules[i].style;
                var uniqueProperties = style.uniqueStyleProperties;
                for (var j = 0; j < uniqueProperties.length; ++j) {
                    var name = uniqueProperties[j];
                    if (style.getPropertyPriority(name).length) {
                        if (!(name in foundPriorityProperties))
                            styleRules[i].usedProperties[name] = true;
                        else
                            delete styleRules[i].usedProperties[name];
                        foundPriorityProperties[name] = true;
                    } else if (name in foundPriorityProperties)
                        delete styleRules[i].usedProperties[name];
                }
            }
        }

        if (refresh) {
            // Walk the style rules and update the sections with new overloaded and used properties.
            for (var i = 0; i < styleRules.length; ++i) {
                var styleRule = styleRules[i];
                var section = styleRule.section;
                if (styleRule.computedStyle)
                    section.disabledComputedProperties = disabledComputedProperties;
                section._usedProperties = (styleRule.usedProperties || usedProperties);
                section.update((section === editedSection) || styleRule.computedStyle);
            }
        } else {
            // Make a property section for each style rule.
            for (var i = 0; i < styleRules.length; ++i) {
                var styleRule = styleRules[i];
                var subtitle = styleRule.subtitle;
                delete styleRule.subtitle;

                var computedStyle = styleRule.computedStyle;
                delete styleRule.computedStyle;

                var ruleUsedProperties = styleRule.usedProperties;
                delete styleRule.usedProperties;

                var editable = styleRule.editable;
                delete styleRule.editable;

                var isAttribute = styleRule.isAttribute;
                delete styleRule.isAttribute;

                // Default editable to true if it was omitted.
                if (typeof editable === "undefined")
                    editable = true;

                var section = new WebInspector.StylePropertiesSection(styleRule, subtitle, computedStyle, (ruleUsedProperties || usedProperties), editable);
                if (computedStyle)
                    section.disabledComputedProperties = disabledComputedProperties;
                section.pane = this;

                if (Preferences.styleRulesExpandedState && section.identifier in Preferences.styleRulesExpandedState)
                    section.expanded = Preferences.styleRulesExpandedState[section.identifier];
                else if (computedStyle)
                    section.collapse(true);
                else if (isAttribute && styleRule.style.length === 0)
                    section.collapse(true);
                else
                    section.expand(true);

                body.appendChild(section.element);
                this.sections.push(section);
            }
        }
    },

    _changeSetting: function(event)
    {
        var options = this.settingsSelectElement.options;
        var selectedOption = options[this.settingsSelectElement.selectedIndex];
        selectedOption.action(event);

        // Select the correct color format setting again, since it needs to be selected.
        var selectedIndex = 0;
        for (var i = 0; i < options.length; ++i) {
            if (options[i].value === Preferences.colorFormat) {
                selectedIndex = i;
                break;
            }
        }

        this.settingsSelectElement.selectedIndex = selectedIndex;
    },

    _changeColorFormat: function(event)
    {
        var selectedOption = this.settingsSelectElement[this.settingsSelectElement.selectedIndex];
        Preferences.colorFormat = selectedOption.value;

        InspectorController.setSetting("color-format", Preferences.colorFormat);

        for (var i = 0; i < this.sections.length; ++i)
            this.sections[i].update(true);
    },

    _createNewRule: function(event)
    {
        this.addBlankSection().startEditingSelector();
    },

    addBlankSection: function()
    {
        var blankSection = new WebInspector.BlankStylePropertiesSection(appropriateSelectorForNode(this.node, true));
        blankSection.pane = this;

        var elementStyleSection = this.sections[1];        
        this.bodyElement.insertBefore(blankSection.element, elementStyleSection.element.nextSibling);

        this.sections.splice(2, 0, blankSection);

        return blankSection;
    },

    removeSection: function(section)
    {
        var index = this.sections.indexOf(section);
        if (index === -1)
            return;
        this.sections.splice(index, 1);
        if (section.element.parentNode)
            section.element.parentNode.removeChild(section.element);
    }
}

WebInspector.StylesSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype;

WebInspector.StylePropertiesSection = function(styleRule, subtitle, computedStyle, usedProperties, editable)
{
    WebInspector.PropertiesSection.call(this, styleRule.selectorText);

    this.titleElement.addEventListener("click", this._clickSelector.bind(this), false);
    this.titleElement.addEventListener("dblclick", this._dblclickSelector.bind(this), false);
    this.element.addEventListener("dblclick", this._dblclickEmptySpace.bind(this), false);

    this.styleRule = styleRule;
    this.rule = this.styleRule.rule;
    this.computedStyle = computedStyle;
    this.editable = (editable && !computedStyle);

    // Prevent editing the user agent and user rules.
    var isUserAgent = this.rule && this.rule.isUserAgent;
    var isUser = this.rule && this.rule.isUser;
    var isViaInspector = this.rule && this.rule.isViaInspector;

    if (isUserAgent || isUser)
        this.editable = false;

    this._usedProperties = usedProperties;

    if (computedStyle) {
        this.element.addStyleClass("computed-style");

        if (Preferences.showInheritedComputedStyleProperties)
            this.element.addStyleClass("show-inherited");

        var showInheritedLabel = document.createElement("label");
        var showInheritedInput = document.createElement("input");
        showInheritedInput.type = "checkbox";
        showInheritedInput.checked = Preferences.showInheritedComputedStyleProperties;

        var computedStyleSection = this;
        var showInheritedToggleFunction = function(event) {
            Preferences.showInheritedComputedStyleProperties = showInheritedInput.checked;
            if (Preferences.showInheritedComputedStyleProperties)
                computedStyleSection.element.addStyleClass("show-inherited");
            else
                computedStyleSection.element.removeStyleClass("show-inherited");
            event.stopPropagation();
        };

        showInheritedLabel.addEventListener("click", showInheritedToggleFunction, false);

        showInheritedLabel.appendChild(showInheritedInput);
        showInheritedLabel.appendChild(document.createTextNode(WebInspector.UIString("Show inherited")));
        this.subtitleElement.appendChild(showInheritedLabel);
    } else {
        if (!subtitle) {
            if (this.styleRule.parentStyleSheet && this.styleRule.parentStyleSheet.href) {
                var url = this.styleRule.parentStyleSheet.href;
                subtitle = WebInspector.linkifyURL(url, WebInspector.displayNameForURL(url));
                this.subtitleElement.addStyleClass("file");
            } else if (isUserAgent)
                subtitle = WebInspector.UIString("user agent stylesheet");
            else if (isUser)
                subtitle = WebInspector.UIString("user stylesheet");
            else if (isViaInspector)
                subtitle = WebInspector.UIString("via inspector");
            else
                subtitle = WebInspector.UIString("inline stylesheet");
        }

        this.subtitle = subtitle;
    }

    this.identifier = styleRule.selectorText;
    if (this.subtitle)
        this.identifier += ":" + this.subtitleElement.textContent;    
}

WebInspector.StylePropertiesSection.prototype = {
    get usedProperties()
    {
        return this._usedProperties || {};
    },

    set usedProperties(x)
    {
        this._usedProperties = x;
        this.update();
    },

    expand: function(dontRememberState)
    {
        WebInspector.PropertiesSection.prototype.expand.call(this);
        if (dontRememberState)
            return;

        if (!Preferences.styleRulesExpandedState)
            Preferences.styleRulesExpandedState = {};
        Preferences.styleRulesExpandedState[this.identifier] = true;
    },

    collapse: function(dontRememberState)
    {
        WebInspector.PropertiesSection.prototype.collapse.call(this);
        if (dontRememberState)
            return;

        if (!Preferences.styleRulesExpandedState)
            Preferences.styleRulesExpandedState = {};
        Preferences.styleRulesExpandedState[this.identifier] = false;
    },

    isPropertyInherited: function(property)
    {
        if (!this.computedStyle || !this._usedProperties || this.noAffect)
            return false;
        // These properties should always show for Computed Style.
        var alwaysShowComputedProperties = { "display": true, "height": true, "width": true };
        return !(property in this.usedProperties) && !(property in alwaysShowComputedProperties) && !(property in this.disabledComputedProperties);
    },

    isPropertyOverloaded: function(property, shorthand)
    {
        if (this.computedStyle || !this._usedProperties || this.noAffect)
            return false;

        var used = (property in this.usedProperties);
        if (used || !shorthand)
            return !used;

        // Find out if any of the individual longhand properties of the shorthand
        // are used, if none are then the shorthand is overloaded too.
        var longhandProperties = this.styleRule.style.getLonghandProperties(property);
        for (var j = 0; j < longhandProperties.length; ++j) {
            var individualProperty = longhandProperties[j];
            if (individualProperty in this.usedProperties)
                return false;
        }

        return true;
    },

    isInspectorStylesheet: function()
    {
        return (this.styleRule.parentStyleSheet === WebInspector.panels.elements.stylesheet);
    },

    update: function(full)
    {
        if (full || this.computedStyle) {
            this.propertiesTreeOutline.removeChildren();
            this.populated = false;
        } else {
            var child = this.propertiesTreeOutline.children[0];
            while (child) {
                child.overloaded = this.isPropertyOverloaded(child.name, child.shorthand);
                child = child.traverseNextTreeElement(false, null, true);
            }
        }

        if (this._afterUpdate) {
            this._afterUpdate(this);
            delete this._afterUpdate;
        }
    },

    onpopulate: function()
    {
        var style = this.styleRule.style;

        var foundShorthands = {};
        var uniqueProperties = style.uniqueStyleProperties;
        var disabledProperties = style.__disabledPropertyValues || {};

        for (var name in disabledProperties)
            uniqueProperties.push(name);

        uniqueProperties.sort();

        for (var i = 0; i < uniqueProperties.length; ++i) {
            var name = uniqueProperties[i];
            var disabled = name in disabledProperties;
            if (!disabled && this.disabledComputedProperties && !(name in this.usedProperties) && name in this.disabledComputedProperties)
                disabled = true;

            var shorthand = !disabled ? style.getPropertyShorthand(name) : null;

            if (shorthand && shorthand in foundShorthands)
                continue;

            if (shorthand) {
                foundShorthands[shorthand] = true;
                name = shorthand;
            }

            var isShorthand = (shorthand ? true : false);
            var inherited = this.isPropertyInherited(name);
            var overloaded = this.isPropertyOverloaded(name, isShorthand);

            var item = new WebInspector.StylePropertyTreeElement(this.styleRule, style, name, isShorthand, inherited, overloaded, disabled);
            this.propertiesTreeOutline.appendChild(item);
        }
    },

    findTreeElementWithName: function(name)
    {
        var treeElement = this.propertiesTreeOutline.children[0];
        while (treeElement) {
            if (treeElement.name === name)
                return treeElement;
            treeElement = treeElement.traverseNextTreeElement(true, null, true);
        }
        return null;
    },

    addNewBlankProperty: function()
    {
        var item = new WebInspector.StylePropertyTreeElement(this.styleRule, this.styleRule.style, "", false, false, false, false);
        this.propertiesTreeOutline.appendChild(item);
        item.listItemElement.textContent = "";
        item._newProperty = true;
        return item;
    },

    _clickSelector: function(event)
    {
        event.stopPropagation();

        // Don't search "Computed Styles", "Style Attribute", or Mapped Attributes.
        if (this.computedStyle || !this.rule || this.rule.isUser)
            return;

        var searchElement = document.getElementById("search");
        searchElement.value = this.styleRule.selectorText;
        WebInspector.performSearch({ target: searchElement });
    },

    _dblclickEmptySpace: function(event)
    {
        this.expand();
        this.addNewBlankProperty().startEditing();
    },

    _dblclickSelector: function(event)
    {
        if (!this.editable)
            return;

        if (!this.rule && this.propertiesTreeOutline.children.length === 0) {
            this.expand();
            this.addNewBlankProperty().startEditing();
            return;
        }

        if (!this.rule)
            return;

        this.startEditingSelector();
        event.stopPropagation();
    },

    startEditingSelector: function()
    {
        var element = this.titleElement;
        if (WebInspector.isBeingEdited(element))
            return;

        WebInspector.startEditing(this.titleElement, this.editingSelectorCommitted.bind(this), this.editingSelectorCancelled.bind(this), null);
        window.getSelection().setBaseAndExtent(element, 0, element, 1);
    },

    editingSelectorCommitted: function(element, newContent, oldContent, context, moveDirection)
    {
        function moveToNextIfNeeded() {
            if (!moveDirection || moveDirection !== "forward")
                return;

            this.expand();
            if (this.propertiesTreeOutline.children.length === 0)
                this.addNewBlankProperty().startEditing();
            else {
                var item = this.propertiesTreeOutline.children[0]
                item.startEditing(item.valueElement);
            }
        }

        if (newContent === oldContent)
            return moveToNextIfNeeded.call(this);

        var self = this;
        function callback(result)
        {
            if (!result) {
                // Invalid Syntax for a Selector
                moveToNextIfNeeded.call(self);
                return;
            }

            var newRulePayload = result[0];
            var doesAffectSelectedNode = result[1];
            if (!doesAffectSelectedNode) {
                self.noAffect = true;
                self.element.addStyleClass("no-affect");
            } else {
                delete self.noAffect;
                self.element.removeStyleClass("no-affect");
            }

            var newRule = WebInspector.CSSStyleDeclaration.parseRule(newRulePayload);
            self.rule = newRule;
            self.styleRule = { section: self, style: newRule.style, selectorText: newRule.selectorText, parentStyleSheet: newRule.parentStyleSheet, rule: newRule };

            var oldIdentifier = this.identifier;
            self.identifier = newRule.selectorText + ":" + self.subtitleElement.textContent;

            self.pane.update();

            WebInspector.panels.elements.renameSelector(oldIdentifier, this.identifier, oldContent, newContent);

            moveToNextIfNeeded.call(self);
        }

        InjectedScriptAccess.applyStyleRuleText(this.rule.id, newContent, this.pane.node.id, callback);
    },

    editingSelectorCancelled: function()
    {
        // Do nothing, this is overridden by BlankStylePropertiesSection.
    }
}

WebInspector.StylePropertiesSection.prototype.__proto__ = WebInspector.PropertiesSection.prototype;

WebInspector.BlankStylePropertiesSection = function(defaultSelectorText)
{
    WebInspector.StylePropertiesSection.call(this, {selectorText: defaultSelectorText, rule: {isViaInspector: true}}, "", false, {}, false);

    this.element.addStyleClass("blank-section");
}

WebInspector.BlankStylePropertiesSection.prototype = {
    expand: function()
    {
        // Do nothing, blank sections are not expandable.
    },

    editingSelectorCommitted: function(element, newContent, oldContent, context)
    {
        var self = this;
        function callback(result)
        {
            if (!result) {
                // Invalid Syntax for a Selector
                self.editingSelectorCancelled();
                return;
            }

            var rule = result[0];
            var doesSelectorAffectSelectedNode = result[1];

            var styleRule = WebInspector.CSSStyleDeclaration.parseRule(rule);
            styleRule.rule = rule;

            self.makeNormal(styleRule);

            if (!doesSelectorAffectSelectedNode) {
                self.noAffect = true;
                self.element.addStyleClass("no-affect");
            }

            self.subtitleElement.textContent = WebInspector.UIString("via inspector");
            self.expand();

            self.addNewBlankProperty().startEditing();
        }

        InjectedScriptAccess.addStyleSelector(newContent, this.pane.node.id, callback);
    },

    editingSelectorCancelled: function()
    {
        this.pane.removeSection(this);
    },

    makeNormal: function(styleRule)
    {
        this.element.removeStyleClass("blank-section");

        this.styleRule = styleRule;
        this.rule = styleRule.rule;
        this.computedStyle = false;
        this.editable = true;
        this.identifier = styleRule.selectorText + ":via inspector";

        this.__proto__ = WebInspector.StylePropertiesSection.prototype;
    }
}

WebInspector.BlankStylePropertiesSection.prototype.__proto__ = WebInspector.StylePropertiesSection.prototype;

WebInspector.StylePropertyTreeElement = function(styleRule, style, name, shorthand, inherited, overloaded, disabled)
{
    this._styleRule = styleRule;
    this.style = style;
    this.name = name;
    this.shorthand = shorthand;
    this._inherited = inherited;
    this._overloaded = overloaded;
    this._disabled = disabled;

    // Pass an empty title, the title gets made later in onattach.
    TreeElement.call(this, "", null, shorthand);
}

WebInspector.StylePropertyTreeElement.prototype = {
    get inherited()
    {
        return this._inherited;
    },

    set inherited(x)
    {
        if (x === this._inherited)
            return;
        this._inherited = x;
        this.updateState();
    },

    get overloaded()
    {
        return this._overloaded;
    },

    set overloaded(x)
    {
        if (x === this._overloaded)
            return;
        this._overloaded = x;
        this.updateState();
    },

    get disabled()
    {
        return this._disabled;
    },

    set disabled(x)
    {
        if (x === this._disabled)
            return;
        this._disabled = x;
        this.updateState();
    },

    get priority()
    {
        if (this.disabled && this.style.__disabledPropertyPriorities && this.name in this.style.__disabledPropertyPriorities)
            return this.style.__disabledPropertyPriorities[this.name];
        return (this.shorthand ? this.style.getShorthandPriority(this.name) : this.style.getPropertyPriority(this.name));
    },

    get value()
    {
        if (this.disabled && this.style.__disabledPropertyValues && this.name in this.style.__disabledPropertyValues)
            return this.style.__disabledPropertyValues[this.name];
        return (this.shorthand ? this.style.getShorthandValue(this.name) : this.style.getPropertyValue(this.name));
    },

    onattach: function()
    {
        this.updateTitle();
    },

    updateTitle: function()
    {
        var priority = this.priority;
        var value = this.value;

        if (priority && !priority.length)
            delete priority;
        if (priority)
            priority = "!" + priority;

        this.updateState();

        var enabledCheckboxElement = document.createElement("input");
        enabledCheckboxElement.className = "enabled-button";
        enabledCheckboxElement.type = "checkbox";
        enabledCheckboxElement.checked = !this.disabled;
        enabledCheckboxElement.addEventListener("change", this.toggleEnabled.bind(this), false);

        var nameElement = document.createElement("span");
        nameElement.className = "name";
        nameElement.textContent = this.name;
        this.nameElement = nameElement;

        var valueElement = document.createElement("span");
        valueElement.className = "value";
        this.valueElement = valueElement;

        if (value) {
            function processValue(regex, processor, nextProcessor, valueText)
            {
                var container = document.createDocumentFragment();

                var items = valueText.replace(regex, "\0$1\0").split("\0");
                for (var i = 0; i < items.length; ++i) {
                    if ((i % 2) === 0) {
                        if (nextProcessor)
                            container.appendChild(nextProcessor(items[i]));
                        else
                            container.appendChild(document.createTextNode(items[i]));
                    } else {
                        var processedNode = processor(items[i]);
                        if (processedNode)
                            container.appendChild(processedNode);
                    }
                }

                return container;
            }

            function linkifyURL(url)
            {
                var container = document.createDocumentFragment();
                container.appendChild(document.createTextNode("url("));
                container.appendChild(WebInspector.linkifyURLAsNode(url, url, null, (url in WebInspector.resourceURLMap)));
                container.appendChild(document.createTextNode(")"));
                return container;
            }

            function processColor(text)
            {
                try {
                    var color = new WebInspector.Color(text);
                } catch (e) {
                    return document.createTextNode(text);
                }

                var swatchElement = document.createElement("span");
                swatchElement.title = WebInspector.UIString("Click to change color format");
                swatchElement.className = "swatch";
                swatchElement.style.setProperty("background-color", text);

                swatchElement.addEventListener("click", changeColorDisplay, false);
                swatchElement.addEventListener("dblclick", function(event) { event.stopPropagation() }, false);

                var format;
                if (Preferences.showColorNicknames && color.nickname)
                    format = "nickname";
                else if (Preferences.colorFormat === "rgb")
                    format = (color.simple ? "rgb" : "rgba");
                else if (Preferences.colorFormat === "hsl")
                    format = (color.simple ? "hsl" : "hsla");
                else if (color.simple)
                    format = (color.hasShortHex() ? "shorthex" : "hex");
                else
                    format = "rgba";

                var colorValueElement = document.createElement("span");
                colorValueElement.textContent = color.toString(format);

                function changeColorDisplay(event)
                {
                    switch (format) {
                        case "rgb":
                            format = "hsl";
                            break;

                        case "shorthex":
                            format = "hex";
                            break;

                        case "hex":
                            format = "rgb";
                            break;

                        case "nickname":
                            if (color.simple) {
                                if (color.hasShortHex())
                                    format = "shorthex";
                                else
                                    format = "hex";
                                break;
                            }

                            format = "rgba";
                            break;

                        case "hsl":
                            if (color.nickname)
                                format = "nickname";
                            else if (color.hasShortHex())
                                format = "shorthex";
                            else
                                format = "hex";
                            break;

                        case "rgba":
                            format = "hsla";
                            break;

                        case "hsla":
                            if (color.nickname)
                                format = "nickname";
                            else
                                format = "rgba";
                            break;
                    }

                    colorValueElement.textContent = color.toString(format);
                }

                var container = document.createDocumentFragment();
                container.appendChild(swatchElement);
                container.appendChild(colorValueElement);
                return container;
            }

            var colorRegex = /((?:rgb|hsl)a?\([^)]+\)|#[0-9a-fA-F]{6}|#[0-9a-fA-F]{3}|\b\w+\b)/g;
            var colorProcessor = processValue.bind(window, colorRegex, processColor, null);

            valueElement.appendChild(processValue(/url\(([^)]+)\)/g, linkifyURL, colorProcessor, value));
        }

        if (priority) {
            var priorityElement = document.createElement("span");
            priorityElement.className = "priority";
            priorityElement.textContent = priority;
        }

        this.listItemElement.removeChildren();

        // Append the checkbox for root elements of an editable section.
        if (this.treeOutline.section && this.treeOutline.section.editable && this.parent.root)
            this.listItemElement.appendChild(enabledCheckboxElement);
        this.listItemElement.appendChild(nameElement);
        this.listItemElement.appendChild(document.createTextNode(": "));
        this.listItemElement.appendChild(valueElement);

        if (priorityElement) {
            this.listItemElement.appendChild(document.createTextNode(" "));
            this.listItemElement.appendChild(priorityElement);
        }

        this.listItemElement.appendChild(document.createTextNode(";"));

        this.tooltip = this.name + ": " + valueElement.textContent + (priority ? " " + priority : "");
    },

    updateAll: function(updateAllRules)
    {
        if (updateAllRules && this.treeOutline.section && this.treeOutline.section.pane)
            this.treeOutline.section.pane.update(null, this.treeOutline.section);
        else if (this.treeOutline.section)
            this.treeOutline.section.update(true);
        else
            this.updateTitle(); // FIXME: this will not show new properties. But we don't hit his case yet.
    },

    toggleEnabled: function(event)
    {
        var disabled = !event.target.checked;

        var self = this;
        function callback(newPayload)
        {
            if (!newPayload)
                return;

            self.style = WebInspector.CSSStyleDeclaration.parseStyle(newPayload);
            self._styleRule.style = self.style;

            // Set the disabled property here, since the code above replies on it not changing
            // until after the value and priority are retrieved.
            self.disabled = disabled;

            if (self.treeOutline.section && self.treeOutline.section.pane)
                self.treeOutline.section.pane.dispatchEventToListeners("style property toggled");

            self.updateAll(true);
        }

        InjectedScriptAccess.toggleStyleEnabled(this.style.id, this.name, disabled, callback);
    },

    updateState: function()
    {
        if (!this.listItemElement)
            return;

        if (this.style.isPropertyImplicit(this.name) || this.value === "initial")
            this.listItemElement.addStyleClass("implicit");
        else
            this.listItemElement.removeStyleClass("implicit");

        if (this.inherited)
            this.listItemElement.addStyleClass("inherited");
        else
            this.listItemElement.removeStyleClass("inherited");

        if (this.overloaded)
            this.listItemElement.addStyleClass("overloaded");
        else
            this.listItemElement.removeStyleClass("overloaded");

        if (this.disabled)
            this.listItemElement.addStyleClass("disabled");
        else
            this.listItemElement.removeStyleClass("disabled");
    },

    onpopulate: function()
    {
        // Only populate once and if this property is a shorthand.
        if (this.children.length || !this.shorthand)
            return;

        var longhandProperties = this.style.getLonghandProperties(this.name);
        for (var i = 0; i < longhandProperties.length; ++i) {
            var name = longhandProperties[i];

            if (this.treeOutline.section) {
                var inherited = this.treeOutline.section.isPropertyInherited(name);
                var overloaded = this.treeOutline.section.isPropertyOverloaded(name);
            }

            var item = new WebInspector.StylePropertyTreeElement(this._styleRule, this.style, name, false, inherited, overloaded);
            this.appendChild(item);
        }
    },

    ondblclick: function(element, event)
    {
        this.startEditing(event.target);
        event.stopPropagation();
    },

    startEditing: function(selectElement)
    {
        // FIXME: we don't allow editing of longhand properties under a shorthand right now.
        if (this.parent.shorthand)
            return;

        if (WebInspector.isBeingEdited(this.listItemElement) || (this.treeOutline.section && !this.treeOutline.section.editable))
            return;

        var context = { expanded: this.expanded, hasChildren: this.hasChildren };

        // Lie about our children to prevent expanding on double click and to collapse shorthands.
        this.hasChildren = false;

        if (!selectElement)
            selectElement = this.listItemElement;

        this.listItemElement.handleKeyEvent = this.editingKeyDown.bind(this);

        WebInspector.startEditing(this.listItemElement, this.editingCommitted.bind(this), this.editingCancelled.bind(this), context);
        window.getSelection().setBaseAndExtent(selectElement, 0, selectElement, 1);
    },

    editingKeyDown: function(event)
    {
        var arrowKeyPressed = (event.keyIdentifier === "Up" || event.keyIdentifier === "Down");
        var pageKeyPressed = (event.keyIdentifier === "PageUp" || event.keyIdentifier === "PageDown");
        if (!arrowKeyPressed && !pageKeyPressed)
            return;

        var selection = window.getSelection();
        if (!selection.rangeCount)
            return;

        var selectionRange = selection.getRangeAt(0);
        if (selectionRange.commonAncestorContainer !== this.listItemElement && !selectionRange.commonAncestorContainer.isDescendant(this.listItemElement))
            return;

        const styleValueDelimeters = " \t\n\"':;,/()";
        var wordRange = selectionRange.startContainer.rangeOfWord(selectionRange.startOffset, styleValueDelimeters, this.listItemElement);
        var wordString = wordRange.toString();
        var replacementString = wordString;

        var matches = /(.*?)(-?\d+(?:\.\d+)?)(.*)/.exec(wordString);
        if (matches && matches.length) {
            var prefix = matches[1];
            var number = parseFloat(matches[2]);
            var suffix = matches[3];

            // If the number is near zero or the number is one and the direction will take it near zero.
            var numberNearZero = (number < 1 && number > -1);
            if (number === 1 && event.keyIdentifier === "Down")
                numberNearZero = true;
            else if (number === -1 && event.keyIdentifier === "Up")
                numberNearZero = true;

            if (numberNearZero && event.altKey && arrowKeyPressed) {
                if (event.keyIdentifier === "Down")
                    number = Math.ceil(number - 1);
                else
                    number = Math.floor(number + 1);
            } else {
                // Jump by 10 when shift is down or jump by 0.1 when near zero or Alt/Option is down.
                // Also jump by 10 for page up and down, or by 100 if shift is held with a page key.
                var changeAmount = 1;
                if (event.shiftKey && pageKeyPressed)
                    changeAmount = 100;
                else if (event.shiftKey || pageKeyPressed)
                    changeAmount = 10;
                else if (event.altKey || numberNearZero)
                    changeAmount = 0.1;

                if (event.keyIdentifier === "Down" || event.keyIdentifier === "PageDown")
                    changeAmount *= -1;

                // Make the new number and constrain it to a precision of 6, this matches numbers the engine returns.
                // Use the Number constructor to forget the fixed precision, so 1.100000 will print as 1.1.
                number = Number((number + changeAmount).toFixed(6));
            }

            replacementString = prefix + number + suffix;
        } else {
            // FIXME: this should cycle through known keywords for the current property name.
            return;
        }

        var replacementTextNode = document.createTextNode(replacementString);

        wordRange.deleteContents();
        wordRange.insertNode(replacementTextNode);

        var finalSelectionRange = document.createRange();
        finalSelectionRange.setStart(replacementTextNode, 0);
        finalSelectionRange.setEnd(replacementTextNode, replacementString.length);

        selection.removeAllRanges();
        selection.addRange(finalSelectionRange);

        event.preventDefault();
        event.handled = true;

        if (!this.originalCSSText) {
            // Remember the rule's original CSS text, so it can be restored
            // if the editing is canceled and before each apply.
            this.originalCSSText = this.style.styleTextWithShorthands();
        } else {
            // Restore the original CSS text before applying user changes. This is needed to prevent
            // new properties from sticking around if the user adds one, then removes it.
            InjectedScriptAccess.setStyleText(this.style.id, this.originalCSSText);
        }

        this.applyStyleText(this.listItemElement.textContent);
    },

    editingEnded: function(context)
    {
        this.hasChildren = context.hasChildren;
        if (context.expanded)
            this.expand();
        delete this.listItemElement.handleKeyEvent;
        delete this.originalCSSText;
    },

    editingCancelled: function(element, context)
    {
        if (this._newProperty)
            this.treeOutline.removeChild(this);
        else if (this.originalCSSText) {
            InjectedScriptAccess.setStyleText(this.style.id, this.originalCSSText);

            if (this.treeOutline.section && this.treeOutline.section.pane)
                this.treeOutline.section.pane.dispatchEventToListeners("style edited");

            this.updateAll();
        } else
            this.updateTitle();

        this.editingEnded(context);
    },

    editingCommitted: function(element, userInput, previousContent, context, moveDirection)
    {
        this.editingEnded(context);

        // Determine where to move to before making changes
        var newProperty, moveToPropertyName, moveToSelector;
        var moveTo = (moveDirection === "forward" ? this.nextSibling : this.previousSibling);
        if (moveTo)
            moveToPropertyName = moveTo.name;
        else if (moveDirection === "forward")
            newProperty = true;
        else if (moveDirection === "backward" && this.treeOutline.section.rule)
            moveToSelector = true;

        // Make the Changes and trigger the moveToNextCallback after updating
        var blankInput = /^\s*$/.test(userInput);
        if (userInput !== previousContent || (this._newProperty && blankInput)) { // only if something changed, or adding a new style and it was blank
            this.treeOutline.section._afterUpdate = moveToNextCallback.bind(this, this._newProperty, !blankInput);
            this.applyStyleText(userInput, true);
        } else
            moveToNextCallback(this._newProperty, false, this.treeOutline.section, false);

        // The Callback to start editing the next property
        function moveToNextCallback(alreadyNew, valueChanged, section)
        {
            if (!moveDirection)
                return;

            // User just tabbed through without changes
            if (moveTo && moveTo.parent) {
                moveTo.startEditing(moveTo.valueElement);
                return;
            }

            // User has made a change then tabbed, wiping all the original treeElements,
            // recalculate the new treeElement for the same property we were going to edit next
            if (moveTo && !moveTo.parent) {
                var treeElement = section.findTreeElementWithName(moveToPropertyName);
                if (treeElement)
                    treeElement.startEditing(treeElement.valueElement);
                return;
            }

            // Create a new attribute in this section
            if (newProperty) {
                if (alreadyNew && !valueChanged)
                    return;

                var item = section.addNewBlankProperty();
                item.startEditing();
                return;
            }

            if (moveToSelector)
                section.startEditingSelector();
        }
    },

    applyStyleText: function(styleText, updateInterface)
    {
        var section = this.treeOutline.section;
        var elementsPanel = WebInspector.panels.elements;
        var styleTextLength = styleText.trimWhitespace().length;
        if (!styleTextLength && updateInterface) {
            if (this._newProperty) {
                // The user deleted everything, so remove the tree element and update.
                this.parent.removeChild(this);
                return;
            } else {
                delete section._afterUpdate;
            }
        }

        var self = this;
        function callback(result)
        {
            if (!result) {
                // The user typed something, but it didn't parse. Just abort and restore
                // the original title for this property.  If this was a new attribute and
                // we couldn't parse, then just remove it.
                if (self._newProperty) {
                    self.parent.removeChild(self);
                    return;
                }
                if (updateInterface)
                    self.updateTitle();
                return;
            }

            var newPayload = result[0];
            var changedProperties = result[1];
            elementsPanel.removeStyleChange(section.identifier, self.style, self.name);

            if (!styleTextLength) {
                // Do remove ourselves from UI when the property removal is confirmed.
                self.parent.removeChild(self);
            } else {
                self.style = WebInspector.CSSStyleDeclaration.parseStyle(newPayload);
                for (var i = 0; i < changedProperties.length; ++i)
                    elementsPanel.addStyleChange(section.identifier, self.style, changedProperties[i]);
                self._styleRule.style = self.style;
            }

            if (section && section.pane)
                section.pane.dispatchEventToListeners("style edited");

            if (updateInterface)
                self.updateAll(true);

            if (!section.rule)
                WebInspector.panels.elements.treeOutline.update();
        }

        InjectedScriptAccess.applyStyleText(this.style.id, styleText.trimWhitespace(), this.name, callback);
    }
}

WebInspector.StylePropertyTreeElement.prototype.__proto__ = TreeElement.prototype;

/* PanelEnablerView.js */

/*
 * Copyright (C) 2008 Apple Inc. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.PanelEnablerView = function(identifier, headingText, disclaimerText, buttonTitle)
{
    WebInspector.View.call(this);

    this.element.addStyleClass("panel-enabler-view");
    this.element.addStyleClass(identifier);

    this.contentElement = document.createElement("div");
    this.contentElement.className = "panel-enabler-view-content";
    this.element.appendChild(this.contentElement);

    this.imageElement = document.createElement("img");
    this.contentElement.appendChild(this.imageElement);

    this.choicesForm = document.createElement("form");
    this.contentElement.appendChild(this.choicesForm);

    this.headerElement = document.createElement("h1");
    this.headerElement.textContent = headingText;
    this.choicesForm.appendChild(this.headerElement);

    var self = this;
    function enableOption(text, checked) {
        var label = document.createElement("label");
        var option = document.createElement("input");
        option.type = "radio";
        option.name = "enable-option";
        if (checked)
            option.checked = true;
        label.appendChild(option);
        label.appendChild(document.createTextNode(text));
        self.choicesForm.appendChild(label);
        return option;
    };

    this.enabledForSession = enableOption(WebInspector.UIString("Only enable for this session"), true);
    this.enabledAlways = enableOption(WebInspector.UIString("Always enable"));

    this.disclaimerElement = document.createElement("div");
    this.disclaimerElement.className = "panel-enabler-disclaimer";
    this.disclaimerElement.textContent = disclaimerText;
    this.choicesForm.appendChild(this.disclaimerElement);

    this.enableButton = document.createElement("button");
    this.enableButton.setAttribute("type", "button");
    this.enableButton.textContent = buttonTitle;
    this.enableButton.addEventListener("click", this._enableButtonCicked.bind(this), false);
    this.choicesForm.appendChild(this.enableButton);

    window.addEventListener("resize", this._windowResized.bind(this), true);
}

WebInspector.PanelEnablerView.prototype = {
    _enableButtonCicked: function()
    {
        this.dispatchEventToListeners("enable clicked");
    },

    _windowResized: function()
    {
        this.imageElement.removeStyleClass("hidden");

        if (this.element.offsetWidth < (this.choicesForm.offsetWidth + this.imageElement.offsetWidth))
            this.imageElement.addStyleClass("hidden");
    },

    get alwaysEnabled() {
        return this.enabledAlways.checked;
    }
}

WebInspector.PanelEnablerView.prototype.__proto__ = WebInspector.View.prototype;

/* StatusBarButton.js */

/*
 * Copyright (C) 2009 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.StatusBarButton = function(title, className)
{
    this.element = document.createElement("button");
    this.element.className = className + " status-bar-item";
    this.element.addEventListener("click", this._clicked.bind(this), false);

    this.glyph = document.createElement("div");
    this.glyph.className = "glyph";
    this.element.appendChild(this.glyph);

    this.glyphShadow = document.createElement("div");
    this.glyphShadow.className = "glyph shadow";
    this.element.appendChild(this.glyphShadow);

    this.title = title;
    this.disabled = false;
    this._toggled = false;
    this._visible = true;
}

WebInspector.StatusBarButton.prototype = {
    _clicked: function()
    {
        this.dispatchEventToListeners("click");
    },

    get disabled()
    {
        return this._disabled;
    },

    set disabled(x)
    {
        if (this._disabled === x)
            return;
        this._disabled = x;
        this.element.disabled = x;
    },

    get title()
    {
        return this._title;
    },

    set title(x)
    {
        if (this._title === x)
            return;
        this._title = x;
        this.element.title = x;
    },

    get toggled()
    {
        return this._toggled;
    },

    set toggled(x)
    {
        if (this._toggled === x)
            return;

        if (x)
            this.element.addStyleClass("toggled-on");
        else
            this.element.removeStyleClass("toggled-on");
        this._toggled = x;
    },

    get visible()
    {
        return this._visible;
    },

    set visible(x)
    {
        if (this._visible === x)
            return;

        if (x)
            this.element.removeStyleClass("hidden");
        else
            this.element.addStyleClass("hidden");
        this._visible = x;
    }
}

WebInspector.StatusBarButton.prototype.__proto__ = WebInspector.Object.prototype;

/* SummaryBar.js */

/*
 * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
 * Copyright (C) 2008, 2009 Anthony Ricaud <rik@webkit.org>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer. 
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution. 
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission. 
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.SummaryBar = function(categories)
{
    this.categories = categories;

    this.element = document.createElement("div");
    this.element.className = "summary-bar";

    this.graphElement = document.createElement("canvas");
    this.graphElement.setAttribute("width", "450");
    this.graphElement.setAttribute("height", "38");
    this.graphElement.className = "summary-graph";
    this.element.appendChild(this.graphElement);

    this.legendElement = document.createElement("div");
    this.legendElement.className = "summary-graph-legend";
    this.element.appendChild(this.legendElement);
}

WebInspector.SummaryBar.prototype = {

    get calculator() {
        return this._calculator;
    },

    set calculator(x) {
        this._calculator = x;
    },

    reset: function()
    {
        this.legendElement.removeChildren();
        this._drawSummaryGraph();
    },

    update: function(data)
    {
        var graphInfo = this.calculator.computeSummaryValues(data);

        var fillSegments = [];

        this.legendElement.removeChildren();

        for (var category in this.categories) {
            var size = graphInfo.categoryValues[category];
            if (!size)
                continue;

            var colorString = this.categories[category].color;

            var fillSegment = {color: colorString, value: size};
            fillSegments.push(fillSegment);

            var legendLabel = this._makeLegendElement(this.categories[category].title, this.calculator.formatValue(size), colorString);
            this.legendElement.appendChild(legendLabel);
        }

        if (graphInfo.total) {
            var totalLegendLabel = this._makeLegendElement(WebInspector.UIString("Total"), this.calculator.formatValue(graphInfo.total));
            totalLegendLabel.addStyleClass("total");
            this.legendElement.appendChild(totalLegendLabel);
        }

        this._drawSummaryGraph(fillSegments);
    },

    _drawSwatch: function(canvas, color)
    {
        var ctx = canvas.getContext("2d");

        function drawSwatchSquare() {
            ctx.fillStyle = color;
            ctx.fillRect(0, 0, 13, 13);

            var gradient = ctx.createLinearGradient(0, 0, 13, 13);
            gradient.addColorStop(0.0, "rgba(255, 255, 255, 0.2)");
            gradient.addColorStop(1.0, "rgba(255, 255, 255, 0.0)");

            ctx.fillStyle = gradient;
            ctx.fillRect(0, 0, 13, 13);

            gradient = ctx.createLinearGradient(13, 13, 0, 0);
            gradient.addColorStop(0.0, "rgba(0, 0, 0, 0.2)");
            gradient.addColorStop(1.0, "rgba(0, 0, 0, 0.0)");

            ctx.fillStyle = gradient;
            ctx.fillRect(0, 0, 13, 13);

            ctx.strokeStyle = "rgba(0, 0, 0, 0.6)";
            ctx.strokeRect(0.5, 0.5, 12, 12);
        }

        ctx.clearRect(0, 0, 13, 24);

        drawSwatchSquare();

        ctx.save();

        ctx.translate(0, 25);
        ctx.scale(1, -1);

        drawSwatchSquare();

        ctx.restore();

        this._fadeOutRect(ctx, 0, 13, 13, 13, 0.5, 0.0);
    },

    _drawSummaryGraph: function(segments)
    {
        if (!segments || !segments.length) {
            segments = [{color: "white", value: 1}];
            this._showingEmptySummaryGraph = true;
        } else
            delete this._showingEmptySummaryGraph;

        // Calculate the total of all segments.
        var total = 0;
        for (var i = 0; i < segments.length; ++i)
            total += segments[i].value;

        // Calculate the percentage of each segment, rounded to the nearest percent.
        var percents = segments.map(function(s) { return Math.max(Math.round(100 * s.value / total), 1) });

        // Calculate the total percentage.
        var percentTotal = 0;
        for (var i = 0; i < percents.length; ++i)
            percentTotal += percents[i];

        // Make sure our percentage total is not greater-than 100, it can be greater
        // if we rounded up for a few segments.
        while (percentTotal > 100) {
            for (var i = 0; i < percents.length && percentTotal > 100; ++i) {
                if (percents[i] > 1) {
                    --percents[i];
                    --percentTotal;
                }
            }
        }

        // Make sure our percentage total is not less-than 100, it can be less
        // if we rounded down for a few segments.
        while (percentTotal < 100) {
            for (var i = 0; i < percents.length && percentTotal < 100; ++i) {
                ++percents[i];
                ++percentTotal;
            }
        }

        var ctx = this.graphElement.getContext("2d");

        var x = 0;
        var y = 0;
        var w = 450;
        var h = 19;
        var r = (h / 2);

        function drawPillShadow()
        {
            // This draws a line with a shadow that is offset away from the line. The line is stroked
            // twice with different X shadow offsets to give more feathered edges. Later we erase the
            // line with destination-out 100% transparent black, leaving only the shadow. This only
            // works if nothing has been drawn into the canvas yet.

            ctx.beginPath();
            ctx.moveTo(x + 4, y + h - 3 - 0.5);
            ctx.lineTo(x + w - 4, y + h - 3 - 0.5);
            ctx.closePath();

            ctx.save();

            ctx.shadowBlur = 2;
            ctx.shadowColor = "rgba(0, 0, 0, 0.5)";
            ctx.shadowOffsetX = 3;
            ctx.shadowOffsetY = 5;

            ctx.strokeStyle = "white";
            ctx.lineWidth = 1;

            ctx.stroke();

            ctx.shadowOffsetX = -3;

            ctx.stroke();

            ctx.restore();

            ctx.save();

            ctx.globalCompositeOperation = "destination-out";
            ctx.strokeStyle = "rgba(0, 0, 0, 1)";
            ctx.lineWidth = 1;

            ctx.stroke();

            ctx.restore();
        }

        function drawPill()
        {
            // Make a rounded rect path.
            ctx.beginPath();
            ctx.moveTo(x, y + r);
            ctx.lineTo(x, y + h - r);
            ctx.quadraticCurveTo(x, y + h, x + r, y + h);
            ctx.lineTo(x + w - r, y + h);
            ctx.quadraticCurveTo(x + w, y + h, x + w, y + h - r);
            ctx.lineTo(x + w, y + r);
            ctx.quadraticCurveTo(x + w, y, x + w - r, y);
            ctx.lineTo(x + r, y);
            ctx.quadraticCurveTo(x, y, x, y + r);
            ctx.closePath();

            // Clip to the rounded rect path.
            ctx.save();
            ctx.clip();

            // Fill the segments with the associated color.
            var previousSegmentsWidth = 0;
            for (var i = 0; i < segments.length; ++i) {
                var segmentWidth = Math.round(w * percents[i] / 100);
                ctx.fillStyle = segments[i].color;
                ctx.fillRect(x + previousSegmentsWidth, y, segmentWidth, h);
                previousSegmentsWidth += segmentWidth;
            }

            // Draw the segment divider lines.
            ctx.lineWidth = 1;
            for (var i = 1; i < 20; ++i) {
                ctx.beginPath();
                ctx.moveTo(x + (i * Math.round(w / 20)) + 0.5, y);
                ctx.lineTo(x + (i * Math.round(w / 20)) + 0.5, y + h);
                ctx.closePath();

                ctx.strokeStyle = "rgba(0, 0, 0, 0.2)";
                ctx.stroke();

                ctx.beginPath();
                ctx.moveTo(x + (i * Math.round(w / 20)) + 1.5, y);
                ctx.lineTo(x + (i * Math.round(w / 20)) + 1.5, y + h);
                ctx.closePath();

                ctx.strokeStyle = "rgba(255, 255, 255, 0.2)";
                ctx.stroke();
            }

            // Draw the pill shading.
            var lightGradient = ctx.createLinearGradient(x, y, x, y + (h / 1.5));
            lightGradient.addColorStop(0.0, "rgba(220, 220, 220, 0.6)");
            lightGradient.addColorStop(0.4, "rgba(220, 220, 220, 0.2)");
            lightGradient.addColorStop(1.0, "rgba(255, 255, 255, 0.0)");

            var darkGradient = ctx.createLinearGradient(x, y + (h / 3), x, y + h);
            darkGradient.addColorStop(0.0, "rgba(0, 0, 0, 0.0)");
            darkGradient.addColorStop(0.8, "rgba(0, 0, 0, 0.2)");
            darkGradient.addColorStop(1.0, "rgba(0, 0, 0, 0.5)");

            ctx.fillStyle = darkGradient;
            ctx.fillRect(x, y, w, h);

            ctx.fillStyle = lightGradient;
            ctx.fillRect(x, y, w, h);

            ctx.restore();
        }

        ctx.clearRect(x, y, w, (h * 2));

        drawPillShadow();
        drawPill();

        ctx.save();

        ctx.translate(0, (h * 2) + 1);
        ctx.scale(1, -1);

        drawPill();

        ctx.restore();

        this._fadeOutRect(ctx, x, y + h + 1, w, h, 0.5, 0.0);
    },

    _fadeOutRect: function(ctx, x, y, w, h, a1, a2)
    {
        ctx.save();

        var gradient = ctx.createLinearGradient(x, y, x, y + h);
        gradient.addColorStop(0.0, "rgba(0, 0, 0, " + (1.0 - a1) + ")");
        gradient.addColorStop(0.8, "rgba(0, 0, 0, " + (1.0 - a2) + ")");
        gradient.addColorStop(1.0, "rgba(0, 0, 0, 1.0)");

        ctx.globalCompositeOperation = "destination-out";

        ctx.fillStyle = gradient;
        ctx.fillRect(x, y, w, h);

        ctx.restore();
    },

    _makeLegendElement: function(label, value, color)
    {
        var legendElement = document.createElement("label");
        legendElement.className = "summary-graph-legend-item";

        if (color) {
            var swatch = document.createElement("canvas");
            swatch.className = "summary-graph-legend-swatch";
            swatch.setAttribute("width", "13");
            swatch.setAttribute("height", "24");

            legendElement.appendChild(swatch);

            this._drawSwatch(swatch, color);
        }

        var labelElement = document.createElement("div");
        labelElement.className = "summary-graph-legend-label";
        legendElement.appendChild(labelElement);

        var headerElement = document.createElement("div");
        headerElement.className = "summary-graph-legend-header";
        headerElement.textContent = label;
        labelElement.appendChild(headerElement);

        var valueElement = document.createElement("div");
        valueElement.className = "summary-graph-legend-value";
        valueElement.textContent = value;
        labelElement.appendChild(valueElement);

        return legendElement;
    }
}

WebInspector.SummaryBar.prototype.__proto__ = WebInspector.Object.prototype;

/* ElementsPanel.js */

/*
 * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
 * Copyright (C) 2008 Matt Lilek <webkit@mattlilek.com>
 * Copyright (C) 2009 Joseph Pecoraro
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.ElementsPanel = function()
{
    WebInspector.Panel.call(this);

    this.element.addStyleClass("elements");

    this.contentElement = document.createElement("div");
    this.contentElement.id = "elements-content";
    this.contentElement.className = "outline-disclosure";

    this.treeOutline = new WebInspector.ElementsTreeOutline();
    this.treeOutline.panel = this;
    this.treeOutline.includeRootDOMNode = false;
    this.treeOutline.selectEnabled = true;

    this.treeOutline.focusedNodeChanged = function(forceUpdate)
    {
        if (this.panel.visible && WebInspector.currentFocusElement !== document.getElementById("search"))
            WebInspector.currentFocusElement = document.getElementById("main-panels");

        this.panel.updateBreadcrumb(forceUpdate);

        for (var pane in this.panel.sidebarPanes)
           this.panel.sidebarPanes[pane].needsUpdate = true;

        this.panel.updateStyles(true);
        this.panel.updateMetrics();
        this.panel.updateProperties();
        this.panel.updateEventListeners();

        if (InspectorController.searchingForNode()) {
            InspectorController.toggleNodeSearch();
            this.panel.nodeSearchButton.toggled = false;
        }
        if (this._focusedDOMNode)
            InjectedScriptAccess.addInspectedNode(this._focusedDOMNode.id, function() {});
    };

    this.contentElement.appendChild(this.treeOutline.element);

    this.crumbsElement = document.createElement("div");
    this.crumbsElement.className = "crumbs";
    this.crumbsElement.addEventListener("mousemove", this._mouseMovedInCrumbs.bind(this), false);
    this.crumbsElement.addEventListener("mouseout", this._mouseMovedOutOfCrumbs.bind(this), false);

    this.sidebarPanes = {};
    this.sidebarPanes.styles = new WebInspector.StylesSidebarPane();
    this.sidebarPanes.metrics = new WebInspector.MetricsSidebarPane();
    this.sidebarPanes.properties = new WebInspector.PropertiesSidebarPane();
    this.sidebarPanes.eventListeners = new WebInspector.EventListenersSidebarPane();

    this.sidebarPanes.styles.onexpand = this.updateStyles.bind(this);
    this.sidebarPanes.metrics.onexpand = this.updateMetrics.bind(this);
    this.sidebarPanes.properties.onexpand = this.updateProperties.bind(this);
    this.sidebarPanes.eventListeners.onexpand = this.updateEventListeners.bind(this);

    this.sidebarPanes.styles.expanded = true;

    this.sidebarPanes.styles.addEventListener("style edited", this._stylesPaneEdited, this);
    this.sidebarPanes.styles.addEventListener("style property toggled", this._stylesPaneEdited, this);
    this.sidebarPanes.metrics.addEventListener("metrics edited", this._metricsPaneEdited, this);

    this.sidebarElement = document.createElement("div");
    this.sidebarElement.id = "elements-sidebar";

    this.sidebarElement.appendChild(this.sidebarPanes.styles.element);
    this.sidebarElement.appendChild(this.sidebarPanes.metrics.element);
    this.sidebarElement.appendChild(this.sidebarPanes.properties.element);
    this.sidebarElement.appendChild(this.sidebarPanes.eventListeners.element);

    this.sidebarResizeElement = document.createElement("div");
    this.sidebarResizeElement.className = "sidebar-resizer-vertical";
    this.sidebarResizeElement.addEventListener("mousedown", this.rightSidebarResizerDragStart.bind(this), false);

    this.nodeSearchButton = new WebInspector.StatusBarButton(WebInspector.UIString("Select an element in the page to inspect it."), "node-search-status-bar-item");
    this.nodeSearchButton.addEventListener("click", this._nodeSearchButtonClicked.bind(this), false);

    this.searchingForNode = false;

    this.element.appendChild(this.contentElement);
    this.element.appendChild(this.sidebarElement);
    this.element.appendChild(this.sidebarResizeElement);

    this._changedStyles = {};

    this.reset();
}

WebInspector.ElementsPanel.prototype = {
    toolbarItemClass: "elements",

    get toolbarItemLabel()
    {
        return WebInspector.UIString("Elements");
    },

    get statusBarItems()
    {
        return [this.nodeSearchButton.element, this.crumbsElement];
    },

    updateStatusBarItems: function()
    {
        this.updateBreadcrumbSizes();
    },

    show: function()
    {
        WebInspector.Panel.prototype.show.call(this);
        this.sidebarResizeElement.style.right = (this.sidebarElement.offsetWidth - 3) + "px";
        this.updateBreadcrumb();
        this.treeOutline.updateSelection();
        if (this.recentlyModifiedNodes.length)
            this._updateModifiedNodes();
    },

    hide: function()
    {
        WebInspector.Panel.prototype.hide.call(this);

        WebInspector.hoveredDOMNode = null;

        if (InspectorController.searchingForNode()) {
            InspectorController.toggleNodeSearch();
            this.nodeSearchButton.toggled = false;
        }
    },

    resize: function()
    {
        this.treeOutline.updateSelection();
        this.updateBreadcrumbSizes();
    },

    reset: function()
    {
        this.rootDOMNode = null;
        this.focusedDOMNode = null;

        WebInspector.hoveredDOMNode = null;

        if (InspectorController.searchingForNode()) {
            InspectorController.toggleNodeSearch();
            this.nodeSearchButton.toggled = false;
        }

        this.recentlyModifiedNodes = [];

        delete this.currentQuery;
        this.searchCanceled();

        var domWindow = WebInspector.domAgent.domWindow;
        if (!domWindow || !domWindow.document || !domWindow.document.firstChild)
            return;

        // If the window isn't visible, return early so the DOM tree isn't built
        // and mutation event listeners are not added.
        if (!InspectorController.isWindowVisible())
            return;

        var inspectedRootDocument = domWindow.document;
        inspectedRootDocument.addEventListener("DOMNodeInserted", this._nodeInserted.bind(this));
        inspectedRootDocument.addEventListener("DOMNodeRemoved", this._nodeRemoved.bind(this));

        this.treeOutline.suppressSelectHighlight = true;
        this.rootDOMNode = inspectedRootDocument;

        var canidateFocusNode = inspectedRootDocument.body || inspectedRootDocument.documentElement;
        if (canidateFocusNode) {
            this.focusedDOMNode = canidateFocusNode;

            if (this.treeOutline.selectedTreeElement)
                this.treeOutline.selectedTreeElement.expand();
        }
        this.treeOutline.suppressSelectHighlight = false;
    },

    searchCanceled: function()
    {
        if (this._searchResults) {
            for (var i = 0; i < this._searchResults.length; ++i) {
                var treeElement = this.treeOutline.findTreeElement(this._searchResults[i]);
                if (treeElement)
                    treeElement.highlighted = false;
            }
        }

        WebInspector.updateSearchMatchesCount(0, this);

        this._currentSearchResultIndex = 0;
        this._searchResults = [];
        InjectedScriptAccess.searchCanceled(function() {});
    },

    performSearch: function(query)
    {
        // Call searchCanceled since it will reset everything we need before doing a new search.
        this.searchCanceled();

        const whitespaceTrimmedQuery = query.trimWhitespace();
        if (!whitespaceTrimmedQuery.length)
            return;

        this._updatedMatchCountOnce = false;
        this._matchesCountUpdateTimeout = null;

        InjectedScriptAccess.performSearch(whitespaceTrimmedQuery, function() {});
    },

    _updateMatchesCount: function()
    {
        WebInspector.updateSearchMatchesCount(this._searchResults.length, this);
        this._matchesCountUpdateTimeout = null;
        this._updatedMatchCountOnce = true;
    },

    _updateMatchesCountSoon: function()
    {
        if (!this._updatedMatchCountOnce)
            return this._updateMatchesCount();
        if (this._matchesCountUpdateTimeout)
            return;
        // Update the matches count every half-second so it doesn't feel twitchy.
        this._matchesCountUpdateTimeout = setTimeout(this._updateMatchesCount.bind(this), 500);
    },

    addNodesToSearchResult: function(nodeIds)
    {
        if (!nodeIds)
            return;

        var nodeIdsArray = nodeIds.split(",");
        for (var i = 0; i < nodeIdsArray.length; ++i) {
            var nodeId = nodeIdsArray[i];
            var node = WebInspector.domAgent.nodeForId(nodeId);
            if (!node)
                continue;

            if (!this._searchResults.length) {
                this._currentSearchResultIndex = 0;

                // Only change the focusedDOMNode if the search was manually performed, because
                // the search may have been performed programmatically and we wouldn't want to
                // change the current focusedDOMNode.
                if (WebInspector.currentFocusElement === document.getElementById("search"))
                    this.focusedDOMNode = node;
            }

            this._searchResults.push(node);

            // Highlight the tree element to show it matched the search.
            // FIXME: highlight the substrings in text nodes and attributes.
            var treeElement = this.treeOutline.findTreeElement(node);
            if (treeElement)
                treeElement.highlighted = true;
        }

        this._updateMatchesCountSoon();
    },

    jumpToNextSearchResult: function()
    {
        if (!this._searchResults || !this._searchResults.length)
            return;
        if (++this._currentSearchResultIndex >= this._searchResults.length)
            this._currentSearchResultIndex = 0;
        this.focusedDOMNode = this._searchResults[this._currentSearchResultIndex];
    },

    jumpToPreviousSearchResult: function()
    {
        if (!this._searchResults || !this._searchResults.length)
            return;
        if (--this._currentSearchResultIndex < 0)
            this._currentSearchResultIndex = (this._searchResults.length - 1);
        this.focusedDOMNode = this._searchResults[this._currentSearchResultIndex];
    },

    renameSelector: function(oldIdentifier, newIdentifier, oldSelector, newSelector)
    {
        // TODO: Implement Shifting the oldSelector, and its contents to a newSelector
    },

    addStyleChange: function(identifier, style, property)
    {
        if (!style.parentRule)
            return;

        var selector = style.parentRule.selectorText;
        if (!this._changedStyles[identifier])
            this._changedStyles[identifier] = {};

        if (!this._changedStyles[identifier][selector])
            this._changedStyles[identifier][selector] = {};

        if (!this._changedStyles[identifier][selector][property])
            WebInspector.styleChanges += 1;

        this._changedStyles[identifier][selector][property] = style.getPropertyValue(property);
    },

    removeStyleChange: function(identifier, style, property)
    {
        if (!style.parentRule)
            return;

        var selector = style.parentRule.selectorText;
        if (!this._changedStyles[identifier] || !this._changedStyles[identifier][selector])
            return;

        if (this._changedStyles[identifier][selector][property]) {
            delete this._changedStyles[identifier][selector][property];
            WebInspector.styleChanges -= 1;
        }
    },

    generateStylesheet: function()
    {
        if (!WebInspector.styleChanges)
            return;

        // Merge Down to Just Selectors
        var mergedSelectors = {};
        for (var identifier in this._changedStyles) {
            for (var selector in this._changedStyles[identifier]) {
                if (!mergedSelectors[selector])
                    mergedSelectors[selector] = this._changedStyles[identifier][selector];
                else { // merge on selector
                    var merge = {};
                    for (var property in mergedSelectors[selector])
                        merge[property] = mergedSelectors[selector][property];
                    for (var property in this._changedStyles[identifier][selector]) {
                        if (!merge[property])
                            merge[property] = this._changedStyles[identifier][selector][property];
                        else { // merge on property within a selector, include comment to notify user
                            var value1 = merge[property];
                            var value2 = this._changedStyles[identifier][selector][property];

                            if (value1 === value2)
                                merge[property] = [value1];
                            else if (value1 instanceof Array)
                                merge[property].push(value2);
                            else
                                merge[property] = [value1, value2];
                        }
                    }
                    mergedSelectors[selector] = merge;
                }
            }
        }

        var builder = [];
        builder.push("/**");
        builder.push(" * Inspector Generated Stylesheet"); // UIString?
        builder.push(" */\n");

        var indent = "  ";
        function displayProperty(property, value, comment) {
            if (comment)
                return indent + "/* " + property + ": " + value + "; */";
            else
                return indent + property + ": " + value + ";";
        }

        for (var selector in mergedSelectors) {
            var psuedoStyle = mergedSelectors[selector];
            var properties = Object.properties(psuedoStyle);
            if (properties.length) {
                builder.push(selector + " {");
                for (var i = 0; i < properties.length; ++i) {
                    var property = properties[i];
                    var value = psuedoStyle[property];
                    if (!(value instanceof Array))
                        builder.push(displayProperty(property, value));
                    else {
                        if (value.length === 1)
                            builder.push(displayProperty(property, value) + " /* merged from equivalent edits */"); // UIString?
                        else {                        
                            builder.push(indent + "/* There was a Conflict... There were Multiple Edits for '" + property + "' */"); // UIString?
                            for (var j = 0; j < value.length; ++j)
                                builder.push(displayProperty(property, value, true));
                        }
                    }
                }
                builder.push("}\n");
            }
        }

        WebInspector.showConsole();
        WebInspector.console.addMessage(new WebInspector.ConsoleTextMessage(builder.join("\n")));
    },

    get rootDOMNode()
    {
        return this.treeOutline.rootDOMNode;
    },

    set rootDOMNode(x)
    {
        this.treeOutline.rootDOMNode = x;
    },

    get focusedDOMNode()
    {
        return this.treeOutline.focusedDOMNode;
    },

    set focusedDOMNode(x)
    {
        this.treeOutline.focusedDOMNode = x;
    },

    _nodeInserted: function(event)
    {
        this.recentlyModifiedNodes.push({node: event.target, parent: event.relatedNode, inserted: true});
        if (this.visible)
            this._updateModifiedNodesSoon();
    },

    _nodeRemoved: function(event)
    {
        this.recentlyModifiedNodes.push({node: event.target, parent: event.relatedNode, removed: true});
        if (this.visible)
            this._updateModifiedNodesSoon();
    },

    _updateModifiedNodesSoon: function()
    {
        if ("_updateModifiedNodesTimeout" in this)
            return;
        this._updateModifiedNodesTimeout = setTimeout(this._updateModifiedNodes.bind(this), 0);
    },

    _updateModifiedNodes: function()
    {
        if ("_updateModifiedNodesTimeout" in this) {
            clearTimeout(this._updateModifiedNodesTimeout);
            delete this._updateModifiedNodesTimeout;
        }

        var updatedParentTreeElements = [];
        var updateBreadcrumbs = false;

        for (var i = 0; i < this.recentlyModifiedNodes.length; ++i) {
            var replaced = this.recentlyModifiedNodes[i].replaced;
            var parent = this.recentlyModifiedNodes[i].parent;
            if (!parent)
                continue;

            var parentNodeItem = this.treeOutline.findTreeElement(parent);
            if (parentNodeItem && !parentNodeItem.alreadyUpdatedChildren) {
                parentNodeItem.updateChildren(replaced);
                parentNodeItem.alreadyUpdatedChildren = true;
                updatedParentTreeElements.push(parentNodeItem);
            }

            if (!updateBreadcrumbs && (this.focusedDOMNode === parent || isAncestorNode(this.focusedDOMNode, parent)))
                updateBreadcrumbs = true;
        }

        for (var i = 0; i < updatedParentTreeElements.length; ++i)
            delete updatedParentTreeElements[i].alreadyUpdatedChildren;

        this.recentlyModifiedNodes = [];

        if (updateBreadcrumbs)
            this.updateBreadcrumb(true);
    },

    _stylesPaneEdited: function()
    {
        this.sidebarPanes.metrics.needsUpdate = true;
        this.updateMetrics();
    },

    _metricsPaneEdited: function()
    {
        this.sidebarPanes.styles.needsUpdate = true;
        this.updateStyles(true);
    },

    _mouseMovedInCrumbs: function(event)
    {
        var nodeUnderMouse = document.elementFromPoint(event.pageX, event.pageY);
        var crumbElement = nodeUnderMouse.enclosingNodeOrSelfWithClass("crumb");

        WebInspector.hoveredDOMNode = (crumbElement ? crumbElement.representedObject : null);

        if ("_mouseOutOfCrumbsTimeout" in this) {
            clearTimeout(this._mouseOutOfCrumbsTimeout);
            delete this._mouseOutOfCrumbsTimeout;
        }
    },

    _mouseMovedOutOfCrumbs: function(event)
    {
        var nodeUnderMouse = document.elementFromPoint(event.pageX, event.pageY);
        if (nodeUnderMouse && nodeUnderMouse.isDescendant(this.crumbsElement))
            return;

        WebInspector.hoveredDOMNode = null;

        this._mouseOutOfCrumbsTimeout = setTimeout(this.updateBreadcrumbSizes.bind(this), 1000);
    },

    updateBreadcrumb: function(forceUpdate)
    {
        if (!this.visible)
            return;

        var crumbs = this.crumbsElement;

        var handled = false;
        var foundRoot = false;
        var crumb = crumbs.firstChild;
        while (crumb) {
            if (crumb.representedObject === this.rootDOMNode)
                foundRoot = true;

            if (foundRoot)
                crumb.addStyleClass("dimmed");
            else
                crumb.removeStyleClass("dimmed");

            if (crumb.representedObject === this.focusedDOMNode) {
                crumb.addStyleClass("selected");
                handled = true;
            } else {
                crumb.removeStyleClass("selected");
            }

            crumb = crumb.nextSibling;
        }

        if (handled && !forceUpdate) {
            // We don't need to rebuild the crumbs, but we need to adjust sizes
            // to reflect the new focused or root node.
            this.updateBreadcrumbSizes();
            return;
        }

        crumbs.removeChildren();

        var panel = this;

        function selectCrumbFunction(event)
        {
            var crumb = event.currentTarget;
            if (crumb.hasStyleClass("collapsed")) {
                // Clicking a collapsed crumb will expose the hidden crumbs.
                if (crumb === panel.crumbsElement.firstChild) {
                    // If the focused crumb is the first child, pick the farthest crumb
                    // that is still hidden. This allows the user to expose every crumb.
                    var currentCrumb = crumb;
                    while (currentCrumb) {
                        var hidden = currentCrumb.hasStyleClass("hidden");
                        var collapsed = currentCrumb.hasStyleClass("collapsed");
                        if (!hidden && !collapsed)
                            break;
                        crumb = currentCrumb;
                        currentCrumb = currentCrumb.nextSibling;
                    }
                }

                panel.updateBreadcrumbSizes(crumb);
            } else {
                // Clicking a dimmed crumb or double clicking (event.detail >= 2)
                // will change the root node in addition to the focused node.
                if (event.detail >= 2 || crumb.hasStyleClass("dimmed"))
                    panel.rootDOMNode = crumb.representedObject.parentNode;
                panel.focusedDOMNode = crumb.representedObject;
            }

            event.preventDefault();
        }

        foundRoot = false;
        for (var current = this.focusedDOMNode; current; current = current.parentNode) {
            if (current.nodeType === Node.DOCUMENT_NODE)
                continue;

            if (current === this.rootDOMNode)
                foundRoot = true;

            var crumb = document.createElement("span");
            crumb.className = "crumb";
            crumb.representedObject = current;
            crumb.addEventListener("mousedown", selectCrumbFunction, false);

            var crumbTitle;
            switch (current.nodeType) {
                case Node.ELEMENT_NODE:
                    crumbTitle = current.nodeName.toLowerCase();

                    var nameElement = document.createElement("span");
                    nameElement.textContent = crumbTitle;
                    crumb.appendChild(nameElement);

                    var idAttribute = current.getAttribute("id");
                    if (idAttribute) {
                        var idElement = document.createElement("span");
                        crumb.appendChild(idElement);

                        var part = "#" + idAttribute;
                        crumbTitle += part;
                        idElement.appendChild(document.createTextNode(part));

                        // Mark the name as extra, since the ID is more important.
                        nameElement.className = "extra";
                    }

                    var classAttribute = current.getAttribute("class");
                    if (classAttribute) {
                        var classes = classAttribute.split(/\s+/);
                        var foundClasses = {};

                        if (classes.length) {
                            var classesElement = document.createElement("span");
                            classesElement.className = "extra";
                            crumb.appendChild(classesElement);

                            for (var i = 0; i < classes.length; ++i) {
                                var className = classes[i];
                                if (className && !(className in foundClasses)) {
                                    var part = "." + className;
                                    crumbTitle += part;
                                    classesElement.appendChild(document.createTextNode(part));
                                    foundClasses[className] = true;
                                }
                            }
                        }
                    }

                    break;

                case Node.TEXT_NODE:
                    if (isNodeWhitespace.call(current))
                        crumbTitle = WebInspector.UIString("(whitespace)");
                    else
                        crumbTitle = WebInspector.UIString("(text)");
                    break

                case Node.COMMENT_NODE:
                    crumbTitle = "<!-->";
                    break;

                case Node.DOCUMENT_TYPE_NODE:
                    crumbTitle = "<!DOCTYPE>";
                    break;

                default:
                    crumbTitle = current.nodeName.toLowerCase();
            }

            if (!crumb.childNodes.length) {
                var nameElement = document.createElement("span");
                nameElement.textContent = crumbTitle;
                crumb.appendChild(nameElement);
            }

            crumb.title = crumbTitle;

            if (foundRoot)
                crumb.addStyleClass("dimmed");
            if (current === this.focusedDOMNode)
                crumb.addStyleClass("selected");
            if (!crumbs.childNodes.length)
                crumb.addStyleClass("end");

            crumbs.appendChild(crumb);
        }

        if (crumbs.hasChildNodes())
            crumbs.lastChild.addStyleClass("start");

        this.updateBreadcrumbSizes();
    },

    updateBreadcrumbSizes: function(focusedCrumb)
    {
        if (!this.visible)
            return;

        if (document.body.offsetWidth <= 0) {
            // The stylesheet hasn't loaded yet or the window is closed,
            // so we can't calculate what is need. Return early.
            return;
        }

        var crumbs = this.crumbsElement;
        if (!crumbs.childNodes.length || crumbs.offsetWidth <= 0)
            return; // No crumbs, do nothing.

        // A Zero index is the right most child crumb in the breadcrumb.
        var selectedIndex = 0;
        var focusedIndex = 0;
        var selectedCrumb;

        var i = 0;
        var crumb = crumbs.firstChild;
        while (crumb) {
            // Find the selected crumb and index. 
            if (!selectedCrumb && crumb.hasStyleClass("selected")) {
                selectedCrumb = crumb;
                selectedIndex = i;
            }

            // Find the focused crumb index. 
            if (crumb === focusedCrumb)
                focusedIndex = i;

            // Remove any styles that affect size before
            // deciding to shorten any crumbs.
            if (crumb !== crumbs.lastChild)
                crumb.removeStyleClass("start");
            if (crumb !== crumbs.firstChild)
                crumb.removeStyleClass("end");

            crumb.removeStyleClass("compact");
            crumb.removeStyleClass("collapsed");
            crumb.removeStyleClass("hidden");

            crumb = crumb.nextSibling;
            ++i;
        }

        // Restore the start and end crumb classes in case they got removed in coalesceCollapsedCrumbs().
        // The order of the crumbs in the document is opposite of the visual order.
        crumbs.firstChild.addStyleClass("end");
        crumbs.lastChild.addStyleClass("start");

        function crumbsAreSmallerThanContainer()
        {
            var rightPadding = 20;
            var errorWarningElement = document.getElementById("error-warning-count");
            if (!WebInspector.drawer.visible && errorWarningElement)
                rightPadding += errorWarningElement.offsetWidth;
            return ((crumbs.totalOffsetLeft + crumbs.offsetWidth + rightPadding) < window.innerWidth);
        }

        if (crumbsAreSmallerThanContainer())
            return; // No need to compact the crumbs, they all fit at full size.

        var BothSides = 0;
        var AncestorSide = -1;
        var ChildSide = 1;

        function makeCrumbsSmaller(shrinkingFunction, direction, significantCrumb)
        {
            if (!significantCrumb)
                significantCrumb = (focusedCrumb || selectedCrumb);

            if (significantCrumb === selectedCrumb)
                var significantIndex = selectedIndex;
            else if (significantCrumb === focusedCrumb)
                var significantIndex = focusedIndex;
            else {
                var significantIndex = 0;
                for (var i = 0; i < crumbs.childNodes.length; ++i) {
                    if (crumbs.childNodes[i] === significantCrumb) {
                        significantIndex = i;
                        break;
                    }
                }
            }

            function shrinkCrumbAtIndex(index)
            {
                var shrinkCrumb = crumbs.childNodes[index];
                if (shrinkCrumb && shrinkCrumb !== significantCrumb)
                    shrinkingFunction(shrinkCrumb);
                if (crumbsAreSmallerThanContainer())
                    return true; // No need to compact the crumbs more.
                return false;
            }

            // Shrink crumbs one at a time by applying the shrinkingFunction until the crumbs
            // fit in the container or we run out of crumbs to shrink.
            if (direction) {
                // Crumbs are shrunk on only one side (based on direction) of the signifcant crumb.
                var index = (direction > 0 ? 0 : crumbs.childNodes.length - 1);
                while (index !== significantIndex) {
                    if (shrinkCrumbAtIndex(index))
                        return true;
                    index += (direction > 0 ? 1 : -1);
                }
            } else {
                // Crumbs are shrunk in order of descending distance from the signifcant crumb,
                // with a tie going to child crumbs.
                var startIndex = 0;
                var endIndex = crumbs.childNodes.length - 1;
                while (startIndex != significantIndex || endIndex != significantIndex) {
                    var startDistance = significantIndex - startIndex;
                    var endDistance = endIndex - significantIndex;
                    if (startDistance >= endDistance)
                        var index = startIndex++;
                    else
                        var index = endIndex--;
                    if (shrinkCrumbAtIndex(index))
                        return true;
                }
            }

            // We are not small enough yet, return false so the caller knows.
            return false;
        }

        function coalesceCollapsedCrumbs()
        {
            var crumb = crumbs.firstChild;
            var collapsedRun = false;
            var newStartNeeded = false;
            var newEndNeeded = false;
            while (crumb) {
                var hidden = crumb.hasStyleClass("hidden");
                if (!hidden) {
                    var collapsed = crumb.hasStyleClass("collapsed"); 
                    if (collapsedRun && collapsed) {
                        crumb.addStyleClass("hidden");
                        crumb.removeStyleClass("compact");
                        crumb.removeStyleClass("collapsed");

                        if (crumb.hasStyleClass("start")) {
                            crumb.removeStyleClass("start");
                            newStartNeeded = true;
                        }

                        if (crumb.hasStyleClass("end")) {
                            crumb.removeStyleClass("end");
                            newEndNeeded = true;
                        }

                        continue;
                    }

                    collapsedRun = collapsed;

                    if (newEndNeeded) {
                        newEndNeeded = false;
                        crumb.addStyleClass("end");
                    }
                } else
                    collapsedRun = true;
                crumb = crumb.nextSibling;
            }

            if (newStartNeeded) {
                crumb = crumbs.lastChild;
                while (crumb) {
                    if (!crumb.hasStyleClass("hidden")) {
                        crumb.addStyleClass("start");
                        break;
                    }
                    crumb = crumb.previousSibling;
                }
            }
        }

        function compact(crumb)
        {
            if (crumb.hasStyleClass("hidden"))
                return;
            crumb.addStyleClass("compact");
        }

        function collapse(crumb, dontCoalesce)
        {
            if (crumb.hasStyleClass("hidden"))
                return;
            crumb.addStyleClass("collapsed");
            crumb.removeStyleClass("compact");
            if (!dontCoalesce)
                coalesceCollapsedCrumbs();
        }

        function compactDimmed(crumb)
        {
            if (crumb.hasStyleClass("dimmed"))
                compact(crumb);
        }

        function collapseDimmed(crumb)
        {
            if (crumb.hasStyleClass("dimmed"))
                collapse(crumb);
        }

        if (!focusedCrumb) {
            // When not focused on a crumb we can be biased and collapse less important
            // crumbs that the user might not care much about.

            // Compact child crumbs.
            if (makeCrumbsSmaller(compact, ChildSide))
                return;

            // Collapse child crumbs.
            if (makeCrumbsSmaller(collapse, ChildSide))
                return;

            // Compact dimmed ancestor crumbs.
            if (makeCrumbsSmaller(compactDimmed, AncestorSide))
                return;

            // Collapse dimmed ancestor crumbs.
            if (makeCrumbsSmaller(collapseDimmed, AncestorSide))
                return;
        }

        // Compact ancestor crumbs, or from both sides if focused.
        if (makeCrumbsSmaller(compact, (focusedCrumb ? BothSides : AncestorSide)))
            return;

        // Collapse ancestor crumbs, or from both sides if focused.
        if (makeCrumbsSmaller(collapse, (focusedCrumb ? BothSides : AncestorSide)))
            return;

        if (!selectedCrumb)
            return;

        // Compact the selected crumb.
        compact(selectedCrumb);
        if (crumbsAreSmallerThanContainer())
            return;

        // Collapse the selected crumb as a last resort. Pass true to prevent coalescing.
        collapse(selectedCrumb, true);
    },

    updateStyles: function(forceUpdate)
    {
        var stylesSidebarPane = this.sidebarPanes.styles;
        if (!stylesSidebarPane.expanded || !stylesSidebarPane.needsUpdate)
            return;

        stylesSidebarPane.update(this.focusedDOMNode, null, forceUpdate);
        stylesSidebarPane.needsUpdate = false;
    },

    updateMetrics: function()
    {
        var metricsSidebarPane = this.sidebarPanes.metrics;
        if (!metricsSidebarPane.expanded || !metricsSidebarPane.needsUpdate)
            return;

        metricsSidebarPane.update(this.focusedDOMNode);
        metricsSidebarPane.needsUpdate = false;
    },

    updateProperties: function()
    {
        var propertiesSidebarPane = this.sidebarPanes.properties;
        if (!propertiesSidebarPane.expanded || !propertiesSidebarPane.needsUpdate)
            return;

        propertiesSidebarPane.update(this.focusedDOMNode);
        propertiesSidebarPane.needsUpdate = false;
    },

    updateEventListeners: function()
    {
        var eventListenersSidebarPane = this.sidebarPanes.eventListeners;
        if (!eventListenersSidebarPane.expanded || !eventListenersSidebarPane.needsUpdate)
            return;

        eventListenersSidebarPane.update(this.focusedDOMNode);
        eventListenersSidebarPane.needsUpdate = false;
    },

    handleKeyEvent: function(event)
    {
        this.treeOutline.handleKeyEvent(event);
    },

    handleCopyEvent: function(event)
    {
        // Don't prevent the normal copy if the user has a selection.
        if (!window.getSelection().isCollapsed)
            return;
        event.clipboardData.clearData();
        event.preventDefault();
        InspectorController.copyNode(this.focusedDOMNode.id);
    },

    rightSidebarResizerDragStart: function(event)
    {
        WebInspector.elementDragStart(this.sidebarElement, this.rightSidebarResizerDrag.bind(this), this.rightSidebarResizerDragEnd.bind(this), event, "col-resize");
    },

    rightSidebarResizerDragEnd: function(event)
    {
        WebInspector.elementDragEnd(event);
    },

    rightSidebarResizerDrag: function(event)
    {
        var x = event.pageX;
        var newWidth = Number.constrain(window.innerWidth - x, Preferences.minElementsSidebarWidth, window.innerWidth * 0.66);

        this.sidebarElement.style.width = newWidth + "px";
        this.contentElement.style.right = newWidth + "px";
        this.sidebarResizeElement.style.right = (newWidth - 3) + "px";

        this.treeOutline.updateSelection();

        event.preventDefault();
    },

    _nodeSearchButtonClicked: function(event)
    {
        InspectorController.toggleNodeSearch();

        this.nodeSearchButton.toggled = InspectorController.searchingForNode();
    }
}

WebInspector.ElementsPanel.prototype.__proto__ = WebInspector.Panel.prototype;

/* ResourcesPanel.js */

/*
 * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
 * Copyright (C) 2008, 2009 Anthony Ricaud <rik@webkit.org>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer. 
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution. 
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission. 
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.ResourcesPanel = function()
{
    WebInspector.AbstractTimelinePanel.call(this);

    this.element.addStyleClass("resources");

    this._createPanelEnabler();

    this.viewsContainerElement = document.createElement("div");
    this.viewsContainerElement.id = "resource-views";
    this.element.appendChild(this.viewsContainerElement);

    this.createFilterPanel();
    this.createInterface();

    this._createStatusbarButtons();

    this.reset();
    this.filter(this.filterAllElement, false);
    this.graphsTreeElement.children[0].select();
}

WebInspector.ResourcesPanel.prototype = {
    toolbarItemClass: "resources",

    get toolbarItemLabel()
    {
        return WebInspector.UIString("Resources");
    },

    get statusBarItems()
    {
        return [this.enableToggleButton.element, this.largerResourcesButton.element, this.sortingSelectElement];
    },

    get categories()
    {
        return WebInspector.resourceCategories;
    },

    createItemTreeElement: function(item)
    {
        return new WebInspector.ResourceSidebarTreeElement(item);
    },

    createItemGraph: function(item)
    {
        return new WebInspector.ResourceGraph(item);
    },

    isCategoryVisible: function(categoryName)
    {
        return (this.itemsGraphsElement.hasStyleClass("filter-all") || this.itemsGraphsElement.hasStyleClass("filter-" + categoryName.toLowerCase()));
    },

    populateSidebar: function()
    {
        var timeGraphItem = new WebInspector.SidebarTreeElement("resources-time-graph-sidebar-item", WebInspector.UIString("Time"));
        timeGraphItem.onselect = this._graphSelected.bind(this);

        var transferTimeCalculator = new WebInspector.ResourceTransferTimeCalculator();
        var transferDurationCalculator = new WebInspector.ResourceTransferDurationCalculator();

        timeGraphItem.sortingOptions = [
            { name: WebInspector.UIString("Sort by Start Time"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByAscendingStartTime, calculator: transferTimeCalculator },
            { name: WebInspector.UIString("Sort by Response Time"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByAscendingResponseReceivedTime, calculator: transferTimeCalculator },
            { name: WebInspector.UIString("Sort by End Time"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByAscendingEndTime, calculator: transferTimeCalculator },
            { name: WebInspector.UIString("Sort by Duration"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByDescendingDuration, calculator: transferDurationCalculator },
            { name: WebInspector.UIString("Sort by Latency"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByDescendingLatency, calculator: transferDurationCalculator },
        ];

        timeGraphItem.selectedSortingOptionIndex = 1;

        var sizeGraphItem = new WebInspector.SidebarTreeElement("resources-size-graph-sidebar-item", WebInspector.UIString("Size"));
        sizeGraphItem.onselect = this._graphSelected.bind(this);

        var transferSizeCalculator = new WebInspector.ResourceTransferSizeCalculator();
        sizeGraphItem.sortingOptions = [
            { name: WebInspector.UIString("Sort by Size"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByDescendingSize, calculator: transferSizeCalculator },
        ];

        sizeGraphItem.selectedSortingOptionIndex = 0;

        this.graphsTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("GRAPHS"), {}, true);
        this.sidebarTree.appendChild(this.graphsTreeElement);

        this.graphsTreeElement.appendChild(timeGraphItem);
        this.graphsTreeElement.appendChild(sizeGraphItem);
        this.graphsTreeElement.expand();

        this.itemsTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("RESOURCES"), {}, true);
        this.sidebarTree.appendChild(this.itemsTreeElement);

        this.itemsTreeElement.expand();
    },

    _createPanelEnabler: function()
    {
        var panelEnablerHeading = WebInspector.UIString("You need to enable resource tracking to use this panel.");
        var panelEnablerDisclaimer = WebInspector.UIString("Enabling resource tracking will reload the page and make page loading slower.");
        var panelEnablerButton = WebInspector.UIString("Enable resource tracking");

        this.panelEnablerView = new WebInspector.PanelEnablerView("resources", panelEnablerHeading, panelEnablerDisclaimer, panelEnablerButton);
        this.panelEnablerView.addEventListener("enable clicked", this._enableResourceTracking, this);

        this.element.appendChild(this.panelEnablerView.element);

        this.enableToggleButton = new WebInspector.StatusBarButton("", "enable-toggle-status-bar-item");
        this.enableToggleButton.addEventListener("click", this._toggleResourceTracking.bind(this), false);
    },

    _createStatusbarButtons: function()
    {
        this.largerResourcesButton = new WebInspector.StatusBarButton(WebInspector.UIString("Use small resource rows."), "resources-larger-resources-status-bar-item");
        this.largerResourcesButton.toggled = Preferences.resourcesLargeRows;
        this.largerResourcesButton.addEventListener("click", this._toggleLargerResources.bind(this), false);
        if (!Preferences.resourcesLargeRows) {
            Preferences.resourcesLargeRows = !Preferences.resourcesLargeRows;
            this._toggleLargerResources(); // this will toggle the preference back to the original
        }

        this.sortingSelectElement = document.createElement("select");
        this.sortingSelectElement.className = "status-bar-item";
        this.sortingSelectElement.addEventListener("change", this._changeSortingFunction.bind(this), false);
    },

    get mainResourceLoadTime()
    {
        return this._mainResourceLoadTime || -1;
    },
    
    set mainResourceLoadTime(x)
    {
        if (this._mainResourceLoadTime === x)
            return;
        
        this._mainResourceLoadTime = x;
        
        // Update the dividers to draw the new line
        this.updateGraphDividersIfNeeded(true);
    },
    
    get mainResourceDOMContentTime()
    {
        return this._mainResourceDOMContentTime || -1;
    },
    
    set mainResourceDOMContentTime(x)
    {
        if (this._mainResourceDOMContentTime === x)
            return;
        
        this._mainResourceDOMContentTime = x;
        
        this.updateGraphDividersIfNeeded(true);
    },

    show: function()
    {
        WebInspector.AbstractTimelinePanel.prototype.show.call(this);

        var visibleView = this.visibleView;
        if (visibleView) {
            visibleView.headersVisible = true;
            visibleView.show(this.viewsContainerElement);
        }

        // Hide any views that are visible that are not this panel's current visible view.
        // This can happen when a ResourceView is visible in the Scripts panel then switched
        // to the this panel.
        var resourcesLength = this._resources.length;
        for (var i = 0; i < resourcesLength; ++i) {
            var resource = this._resources[i];
            var view = resource._resourcesView;
            if (!view || view === visibleView)
                continue;
            view.visible = false;
        }
    },

    resize: function()
    {
        WebInspector.AbstractTimelinePanel.prototype.resize.call(this);

        var visibleView = this.visibleView;
        if (visibleView && "resize" in visibleView)
            visibleView.resize();
    },

    get searchableViews()
    {
        var views = [];

        const visibleView = this.visibleView;
        if (visibleView && visibleView.performSearch)
            views.push(visibleView);

        var resourcesLength = this._resources.length;
        for (var i = 0; i < resourcesLength; ++i) {
            var resource = this._resources[i];
            if (!resource._itemsTreeElement)
                continue;
            var resourceView = this.resourceViewForResource(resource);
            if (!resourceView.performSearch || resourceView === visibleView)
                continue;
            views.push(resourceView);
        }

        return views;
    },

    get searchResultsSortFunction()
    {
        const resourceTreeElementSortFunction = this.sortingFunction;

        function sortFuction(a, b)
        {
            return resourceTreeElementSortFunction(a.resource._itemsTreeElement, b.resource._itemsTreeElement);
        }

        return sortFuction;
    },

    searchMatchFound: function(view, matches)
    {
        view.resource._itemsTreeElement.searchMatches = matches;
    },

    searchCanceled: function(startingNewSearch)
    {
        WebInspector.Panel.prototype.searchCanceled.call(this, startingNewSearch);

        if (startingNewSearch || !this._resources)
            return;

        for (var i = 0; i < this._resources.length; ++i) {
            var resource = this._resources[i];
            if (resource._itemsTreeElement)
                resource._itemsTreeElement.updateErrorsAndWarnings();
        }
    },

    performSearch: function(query)
    {
        for (var i = 0; i < this._resources.length; ++i) {
            var resource = this._resources[i];
            if (resource._itemsTreeElement)
                resource._itemsTreeElement.resetBubble();
        }

        WebInspector.Panel.prototype.performSearch.call(this, query);
    },

    get visibleView()
    {
        if (this.visibleResource)
            return this.visibleResource._resourcesView;
        return null;
    },

    get sortingFunction()
    {
        return this._sortingFunction;
    },

    set sortingFunction(x)
    {
        this._sortingFunction = x;
        this._sortResourcesIfNeeded();
    },

    refresh: function()
    {
        WebInspector.AbstractTimelinePanel.prototype.refresh.call(this);

        this._sortResourcesIfNeeded();
        this._updateSummaryGraph();
    },

    _updateSummaryGraph: function()
    {
        this.summaryBar.update(this._resources);
    },

    resourceTrackingWasEnabled: function()
    {
        this.reset();
    },

    resourceTrackingWasDisabled: function()
    {
        this.reset();
    },

    reset: function()
    {
        this.closeVisibleResource();

        delete this.currentQuery;
        this.searchCanceled();

        if (this._resources) {
            var resourcesLength = this._resources.length;
            for (var i = 0; i < resourcesLength; ++i) {
                var resource = this._resources[i];

                resource.warnings = 0;
                resource.errors = 0;

                delete resource._resourcesView;
            }
        }

        WebInspector.AbstractTimelinePanel.prototype.reset.call(this);
        
        this.mainResourceLoadTime = -1;
        this.mainResourceDOMContentTime = -1;
 
        this.viewsContainerElement.removeChildren();

        this.summaryBar.reset();

        if (InspectorController.resourceTrackingEnabled()) {
            this.enableToggleButton.title = WebInspector.UIString("Resource tracking enabled. Click to disable.");
            this.enableToggleButton.toggled = true;
            this.largerResourcesButton.visible = true;
            this.sortingSelectElement.removeStyleClass("hidden");
            this.panelEnablerView.visible = false;
        } else {
            this.enableToggleButton.title = WebInspector.UIString("Resource tracking disabled. Click to enable.");
            this.enableToggleButton.toggled = false;
            this.largerResourcesButton.visible = false;
            this.sortingSelectElement.addStyleClass("hidden");
            this.panelEnablerView.visible = true;
        }
    },

    addResource: function(resource)
    {
        this._resources.push(resource);
        this.refreshResource(resource);
    },

    removeResource: function(resource)
    {
        if (this.visibleView === resource._resourcesView)
            this.closeVisibleResource();

        this.removeItem(resource);

        resource.warnings = 0;
        resource.errors = 0;

        delete resource._resourcesView;
    },

    addMessageToResource: function(resource, msg)
    {
        if (!resource)
            return;

        switch (msg.level) {
        case WebInspector.ConsoleMessage.MessageLevel.Warning:
            resource.warnings += msg.repeatDelta;
            break;
        case WebInspector.ConsoleMessage.MessageLevel.Error:
            resource.errors += msg.repeatDelta;
            break;
        }

        if (!this.currentQuery && resource._itemsTreeElement)
            resource._itemsTreeElement.updateErrorsAndWarnings();

        var view = this.resourceViewForResource(resource);
        if (view.addMessage)
            view.addMessage(msg);
    },

    clearMessages: function()
    {
        var resourcesLength = this._resources.length;
        for (var i = 0; i < resourcesLength; ++i) {
            var resource = this._resources[i];
            resource.warnings = 0;
            resource.errors = 0;

            if (!this.currentQuery && resource._itemsTreeElement)
                resource._itemsTreeElement.updateErrorsAndWarnings();

            var view = resource._resourcesView;
            if (!view || !view.clearMessages)
                continue;
            view.clearMessages();
        }
    },

    refreshResource: function(resource)
    {
        this.refreshItem(resource);
    },

    recreateViewForResourceIfNeeded: function(resource)
    {
        if (!resource || !resource._resourcesView)
            return;

        var newView = this._createResourceView(resource);
        if (newView.prototype === resource._resourcesView.prototype)
            return;

        resource.warnings = 0;
        resource.errors = 0;

        if (!this.currentQuery && resource._itemsTreeElement)
            resource._itemsTreeElement.updateErrorsAndWarnings();

        var oldView = resource._resourcesView;

        resource._resourcesView.detach();
        delete resource._resourcesView;

        resource._resourcesView = newView;

        newView.headersVisible = oldView.headersVisible;

        if (oldView.visible && oldView.element.parentNode)
            newView.show(oldView.element.parentNode);
    },

    showResource: function(resource, line)
    {
        if (!resource)
            return;

        this.containerElement.addStyleClass("viewing-resource");

        if (this.visibleResource && this.visibleResource._resourcesView)
            this.visibleResource._resourcesView.hide();

        var view = this.resourceViewForResource(resource);
        view.headersVisible = true;
        view.show(this.viewsContainerElement);

        if (line) {
            if (view.revealLine)
                view.revealLine(line);
            if (view.highlightLine)
                view.highlightLine(line);
        }

        this.revealAndSelectItem(resource);

        this.visibleResource = resource;

        this.updateSidebarWidth();
    },

    showView: function(view)
    {
        if (!view)
            return;
        this.showResource(view.resource);
    },

    closeVisibleResource: function()
    {
        this.containerElement.removeStyleClass("viewing-resource");
        this._updateDividersLabelBarPosition();

        if (this.visibleResource && this.visibleResource._resourcesView)
            this.visibleResource._resourcesView.hide();
        delete this.visibleResource;

        if (this._lastSelectedGraphTreeElement)
            this._lastSelectedGraphTreeElement.select(true);

        this.updateSidebarWidth();
    },

    resourceViewForResource: function(resource)
    {
        if (!resource)
            return null;
        if (!resource._resourcesView)
            resource._resourcesView = this._createResourceView(resource);
        return resource._resourcesView;
    },

    sourceFrameForResource: function(resource)
    {
        var view = this.resourceViewForResource(resource);
        if (!view)
            return null;

        if (!view.setupSourceFrameIfNeeded)
            return null;

        // Setting up the source frame requires that we be attached.
        if (!this.element.parentNode)
            this.attach();

        view.setupSourceFrameIfNeeded();
        return view.sourceFrame;
    },

    _sortResourcesIfNeeded: function()
    {
        this.sortItems(this.sortingFunction);
    },

    updateGraphDividersIfNeeded: function(force)
    {
        var proceed = WebInspector.AbstractTimelinePanel.prototype.updateGraphDividersIfNeeded.call(this, force);
        
        if (!proceed)
            return;

        if (this.calculator.startAtZero || !this.calculator.computePercentageFromEventTime) {
            // If our current sorting method starts at zero, that means it shows all
            // resources starting at the same point, and so onLoad event and DOMContent
            // event lines really wouldn't make much sense here, so don't render them.
            // Additionally, if the calculator doesn't have the computePercentageFromEventTime
            // function defined, we are probably sorting by size, and event times aren't relevant
            // in this case.
            return;
        }

        if (this.mainResourceLoadTime !== -1) {
            var percent = this.calculator.computePercentageFromEventTime(this.mainResourceLoadTime);

            var loadDivider = document.createElement("div");
            loadDivider.className = "resources-onload-divider";

            var loadDividerPadding = document.createElement("div");
            loadDividerPadding.className = "resources-event-divider-padding";
            loadDividerPadding.style.left = percent + "%";
            loadDividerPadding.title = WebInspector.UIString("Load event fired");
            loadDividerPadding.appendChild(loadDivider);

            this.addEventDivider(loadDividerPadding);
        }
        
        if (this.mainResourceDOMContentTime !== -1) {
            var percent = this.calculator.computePercentageFromEventTime(this.mainResourceDOMContentTime);

            var domContentDivider = document.createElement("div");
            domContentDivider.className = "resources-ondomcontent-divider";
            
            var domContentDividerPadding = document.createElement("div");
            domContentDividerPadding.className = "resources-event-divider-padding";
            domContentDividerPadding.style.left = percent + "%";
            domContentDividerPadding.title = WebInspector.UIString("DOMContent event fired");
            domContentDividerPadding.appendChild(domContentDivider);

            this.addEventDivider(domContentDividerPadding);
        }
    },

    _graphSelected: function(treeElement)
    {
        if (this._lastSelectedGraphTreeElement)
            this._lastSelectedGraphTreeElement.selectedSortingOptionIndex = this.sortingSelectElement.selectedIndex;

        this._lastSelectedGraphTreeElement = treeElement;

        this.sortingSelectElement.removeChildren();
        for (var i = 0; i < treeElement.sortingOptions.length; ++i) {
            var sortingOption = treeElement.sortingOptions[i];
            var option = document.createElement("option");
            option.label = sortingOption.name;
            option.sortingFunction = sortingOption.sortingFunction;
            option.calculator = sortingOption.calculator;
            this.sortingSelectElement.appendChild(option);
        }

        this.sortingSelectElement.selectedIndex = treeElement.selectedSortingOptionIndex;
        this._changeSortingFunction();

        this.closeVisibleResource();
        this.containerElement.scrollTop = 0;
    },

    _toggleLargerResources: function()
    {
        if (!this.itemsTreeElement._childrenListNode)
            return;

        this.itemsTreeElement.smallChildren = !this.itemsTreeElement.smallChildren;
        Preferences.resourcesLargeRows = !Preferences.resourcesLargeRows;
        InspectorController.setSetting("resources-large-rows", Preferences.resourcesLargeRows);

        if (this.itemsTreeElement.smallChildren) {
            this.itemsGraphsElement.addStyleClass("small");
            this.largerResourcesButton.title = WebInspector.UIString("Use large resource rows.");
            this.largerResourcesButton.toggled = false;
            this.adjustScrollPosition();
        } else {
            this.itemsGraphsElement.removeStyleClass("small");
            this.largerResourcesButton.title = WebInspector.UIString("Use small resource rows.");
            this.largerResourcesButton.toggled = true;
        }
    },

    _changeSortingFunction: function()
    {
        var selectedOption = this.sortingSelectElement[this.sortingSelectElement.selectedIndex];
        this.sortingFunction = selectedOption.sortingFunction;
        this.calculator = this.summaryBar.calculator = selectedOption.calculator;
    },

    _createResourceView: function(resource)
    {
        switch (resource.category) {
            case WebInspector.resourceCategories.documents:
            case WebInspector.resourceCategories.stylesheets:
            case WebInspector.resourceCategories.scripts:
            case WebInspector.resourceCategories.xhr:
                return new WebInspector.SourceView(resource);
            case WebInspector.resourceCategories.images:
                return new WebInspector.ImageView(resource);
            case WebInspector.resourceCategories.fonts:
                return new WebInspector.FontView(resource);
            default:
                return new WebInspector.ResourceView(resource);
        }
    },

    setSidebarWidth: function(width)
    {
        if (this.visibleResource) {
            this.containerElement.style.width = width + "px";
            this.sidebarElement.style.removeProperty("width");
        } else {
            this.sidebarElement.style.width = width + "px";
            this.containerElement.style.removeProperty("width");
        }

        this.sidebarResizeElement.style.left = (width - 3) + "px";
    },

    updateMainViewWidth: function(width)
    {
        WebInspector.AbstractTimelinePanel.prototype.updateMainViewWidth.call(this, width);
        this.viewsContainerElement.style.left = width + "px";
    },

    _enableResourceTracking: function()
    {
        if (InspectorController.resourceTrackingEnabled())
            return;
        this._toggleResourceTracking(this.panelEnablerView.alwaysEnabled);
    },

    _toggleResourceTracking: function(optionalAlways)
    {
        if (InspectorController.resourceTrackingEnabled()) {
            this.largerResourcesButton.visible = false;
            this.sortingSelectElement.visible = false;
            InspectorController.disableResourceTracking(true);
        } else {
            this.largerResourcesButton.visible = true;
            this.sortingSelectElement.visible = true;
            InspectorController.enableResourceTracking(!!optionalAlways);
        }
    },

    get _resources()
    {
        return this.items;
    }
}

WebInspector.ResourcesPanel.prototype.__proto__ = WebInspector.AbstractTimelinePanel.prototype;

WebInspector.ResourceTimeCalculator = function(startAtZero)
{
    WebInspector.AbstractTimelineCalculator.call(this);
    this.startAtZero = startAtZero;
}

WebInspector.ResourceTimeCalculator.prototype = {
    computeSummaryValues: function(resources)
    {
        var resourcesByCategory = {};
        var resourcesLength = resources.length;
        for (var i = 0; i < resourcesLength; ++i) {
            var resource = resources[i];
            if (!(resource.category.name in resourcesByCategory))
                resourcesByCategory[resource.category.name] = [];
            resourcesByCategory[resource.category.name].push(resource);
        }

        var earliestStart;
        var latestEnd;
        var categoryValues = {};
        for (var category in resourcesByCategory) {
            resourcesByCategory[category].sort(WebInspector.Resource.CompareByTime);
            categoryValues[category] = 0;

            var segment = {start: -1, end: -1};

            var categoryResources = resourcesByCategory[category];
            var resourcesLength = categoryResources.length;
            for (var i = 0; i < resourcesLength; ++i) {
                var resource = categoryResources[i];
                if (resource.startTime === -1 || resource.endTime === -1)
                    continue;

                if (typeof earliestStart === "undefined")
                    earliestStart = resource.startTime;
                else
                    earliestStart = Math.min(earliestStart, resource.startTime);

                if (typeof latestEnd === "undefined")
                    latestEnd = resource.endTime;
                else
                    latestEnd = Math.max(latestEnd, resource.endTime);

                if (resource.startTime <= segment.end) {
                    segment.end = Math.max(segment.end, resource.endTime);
                    continue;
                }

                categoryValues[category] += segment.end - segment.start;

                segment.start = resource.startTime;
                segment.end = resource.endTime;
            }

            // Add the last segment
            categoryValues[category] += segment.end - segment.start;
        }

        return {categoryValues: categoryValues, total: latestEnd - earliestStart};
    },

    computeBarGraphPercentages: function(resource)
    {
        if (resource.startTime !== -1)
            var start = ((resource.startTime - this.minimumBoundary) / this.boundarySpan) * 100;
        else
            var start = 0;

        if (resource.responseReceivedTime !== -1)
            var middle = ((resource.responseReceivedTime - this.minimumBoundary) / this.boundarySpan) * 100;
        else
            var middle = (this.startAtZero ? start : 100);

        if (resource.endTime !== -1)
            var end = ((resource.endTime - this.minimumBoundary) / this.boundarySpan) * 100;
        else
            var end = (this.startAtZero ? middle : 100);

        if (this.startAtZero) {
            end -= start;
            middle -= start;
            start = 0;
        }

        return {start: start, middle: middle, end: end};
    },
    
    computePercentageFromEventTime: function(eventTime)
    {
        // This function computes a percentage in terms of the total loading time
        // of a specific event. If startAtZero is set, then this is useless, and we
        // want to return 0.
        if (eventTime !== -1 && !this.startAtZero)
            return ((eventTime - this.minimumBoundary) / this.boundarySpan) * 100;

        return 0;
    },

    computeBarGraphLabels: function(resource)
    {
        var leftLabel = "";
        if (resource.latency > 0)
            leftLabel = this.formatValue(resource.latency);

        var rightLabel = "";
        if (resource.responseReceivedTime !== -1 && resource.endTime !== -1)
            rightLabel = this.formatValue(resource.endTime - resource.responseReceivedTime);

        if (leftLabel && rightLabel) {
            var total = this.formatValue(resource.duration);
            var tooltip = WebInspector.UIString("%s latency, %s download (%s total)", leftLabel, rightLabel, total);
        } else if (leftLabel)
            var tooltip = WebInspector.UIString("%s latency", leftLabel);
        else if (rightLabel)
            var tooltip = WebInspector.UIString("%s download", rightLabel);

        if (resource.cached)
            tooltip = WebInspector.UIString("%s (from cache)", tooltip);

        return {left: leftLabel, right: rightLabel, tooltip: tooltip};
    },

    updateBoundaries: function(resource)
    {
        var didChange = false;

        var lowerBound;
        if (this.startAtZero)
            lowerBound = 0;
        else
            lowerBound = this._lowerBound(resource);

        if (lowerBound !== -1 && (typeof this.minimumBoundary === "undefined" || lowerBound < this.minimumBoundary)) {
            this.minimumBoundary = lowerBound;
            didChange = true;
        }

        var upperBound = this._upperBound(resource);
        if (upperBound !== -1 && (typeof this.maximumBoundary === "undefined" || upperBound > this.maximumBoundary)) {
            this.maximumBoundary = upperBound;
            didChange = true;
        }

        return didChange;
    },

    formatValue: function(value)
    {
        return Number.secondsToString(value, WebInspector.UIString.bind(WebInspector));
    },

    _lowerBound: function(resource)
    {
        return 0;
    },

    _upperBound: function(resource)
    {
        return 0;
    }
}

WebInspector.ResourceTimeCalculator.prototype.__proto__ = WebInspector.AbstractTimelineCalculator.prototype;

WebInspector.ResourceTransferTimeCalculator = function()
{
    WebInspector.ResourceTimeCalculator.call(this, false);
}

WebInspector.ResourceTransferTimeCalculator.prototype = {
    formatValue: function(value)
    {
        return Number.secondsToString(value, WebInspector.UIString.bind(WebInspector));
    },

    _lowerBound: function(resource)
    {
        return resource.startTime;
    },

    _upperBound: function(resource)
    {
        return resource.endTime;
    }
}

WebInspector.ResourceTransferTimeCalculator.prototype.__proto__ = WebInspector.ResourceTimeCalculator.prototype;

WebInspector.ResourceTransferDurationCalculator = function()
{
    WebInspector.ResourceTimeCalculator.call(this, true);
}

WebInspector.ResourceTransferDurationCalculator.prototype = {
    formatValue: function(value)
    {
        return Number.secondsToString(value, WebInspector.UIString.bind(WebInspector));
    },

    _upperBound: function(resource)
    {
        return resource.duration;
    }
}

WebInspector.ResourceTransferDurationCalculator.prototype.__proto__ = WebInspector.ResourceTimeCalculator.prototype;

WebInspector.ResourceTransferSizeCalculator = function()
{
    WebInspector.AbstractTimelineCalculator.call(this);
}

WebInspector.ResourceTransferSizeCalculator.prototype = {
    computeBarGraphLabels: function(resource)
    {
        const label = this.formatValue(this._value(resource));
        var tooltip = label;
        if (resource.cached)
            tooltip = WebInspector.UIString("%s (from cache)", tooltip);
        return {left: label, right: label, tooltip: tooltip};
    },

    _value: function(resource)
    {
        return resource.contentLength;
    },

    formatValue: function(value)
    {
        return Number.bytesToString(value, WebInspector.UIString.bind(WebInspector));
    }
}

WebInspector.ResourceTransferSizeCalculator.prototype.__proto__ = WebInspector.AbstractTimelineCalculator.prototype;

WebInspector.ResourceSidebarTreeElement = function(resource)
{
    this.resource = resource;

    this.createIconElement();

    WebInspector.SidebarTreeElement.call(this, "resource-sidebar-tree-item", "", "", resource);

    this.refreshTitles();
}

WebInspector.ResourceSidebarTreeElement.prototype = {
    onattach: function()
    {
        WebInspector.SidebarTreeElement.prototype.onattach.call(this);

        this._listItemNode.addStyleClass("resources-category-" + this.resource.category.name);
        this._listItemNode.draggable = true;
        
        // FIXME: should actually add handler to parent, to be resolved via
        // https://bugs.webkit.org/show_bug.cgi?id=30227
        this._listItemNode.addEventListener("dragstart", this.ondragstart.bind(this), false);
        this.updateErrorsAndWarnings();
    },

    onselect: function()
    {
        WebInspector.panels.resources.showResource(this.resource);
    },
    
    ondblclick: function(treeElement, event)
    {
        InjectedScriptAccess.openInInspectedWindow(this.resource.url, function() {});
    },

    ondragstart: function(event) {
        event.dataTransfer.setData("text/plain", this.resource.url);
        event.dataTransfer.setData("text/uri-list", this.resource.url + "\r\n");
        event.dataTransfer.effectAllowed = "copy";
        return true;
    },

    get mainTitle()
    {
        return this.resource.displayName;
    },

    set mainTitle(x)
    {
        // Do nothing.
    },

    get subtitle()
    {
        var subtitle = this.resource.displayDomain;

        if (this.resource.path && this.resource.lastPathComponent) {
            var lastPathComponentIndex = this.resource.path.lastIndexOf("/" + this.resource.lastPathComponent);
            if (lastPathComponentIndex != -1)
                subtitle += this.resource.path.substring(0, lastPathComponentIndex);
        }

        return subtitle;
    },

    set subtitle(x)
    {
        // Do nothing.
    },

    get selectable()
    {
        return WebInspector.panels.resources.isCategoryVisible(this.resource.category.name);
    },

    createIconElement: function()
    {
        var previousIconElement = this.iconElement;

        if (this.resource.category === WebInspector.resourceCategories.images) {
            var previewImage = document.createElement("img");
            previewImage.className = "image-resource-icon-preview";
            previewImage.src = this.resource.url;

            this.iconElement = document.createElement("div");
            this.iconElement.className = "icon";
            this.iconElement.appendChild(previewImage);
        } else {
            this.iconElement = document.createElement("img");
            this.iconElement.className = "icon";
        }

        if (previousIconElement)
            previousIconElement.parentNode.replaceChild(this.iconElement, previousIconElement);
    },

    refresh: function()
    {
        this.refreshTitles();

        if (!this._listItemNode.hasStyleClass("resources-category-" + this.resource.category.name)) {
            this._listItemNode.removeMatchingStyleClasses("resources-category-\\w+");
            this._listItemNode.addStyleClass("resources-category-" + this.resource.category.name);

            this.createIconElement();
        }
    },

    resetBubble: function()
    {
        this.bubbleText = "";
        this.bubbleElement.removeStyleClass("search-matches");
        this.bubbleElement.removeStyleClass("warning");
        this.bubbleElement.removeStyleClass("error");
    },

    set searchMatches(matches)
    {
        this.resetBubble();

        if (!matches)
            return;

        this.bubbleText = matches;
        this.bubbleElement.addStyleClass("search-matches");
    },

    updateErrorsAndWarnings: function()
    {
        this.resetBubble();

        if (this.resource.warnings || this.resource.errors)
            this.bubbleText = (this.resource.warnings + this.resource.errors);

        if (this.resource.warnings)
            this.bubbleElement.addStyleClass("warning");

        if (this.resource.errors)
            this.bubbleElement.addStyleClass("error");
    }
}

WebInspector.ResourceSidebarTreeElement.CompareByAscendingStartTime = function(a, b)
{
    return WebInspector.Resource.CompareByStartTime(a.resource, b.resource)
        || WebInspector.Resource.CompareByEndTime(a.resource, b.resource)
        || WebInspector.Resource.CompareByResponseReceivedTime(a.resource, b.resource);
}

WebInspector.ResourceSidebarTreeElement.CompareByAscendingResponseReceivedTime = function(a, b)
{
    return WebInspector.Resource.CompareByResponseReceivedTime(a.resource, b.resource)
        || WebInspector.Resource.CompareByStartTime(a.resource, b.resource)
        || WebInspector.Resource.CompareByEndTime(a.resource, b.resource);
}

WebInspector.ResourceSidebarTreeElement.CompareByAscendingEndTime = function(a, b)
{
    return WebInspector.Resource.CompareByEndTime(a.resource, b.resource)
        || WebInspector.Resource.CompareByStartTime(a.resource, b.resource)
        || WebInspector.Resource.CompareByResponseReceivedTime(a.resource, b.resource);
}

WebInspector.ResourceSidebarTreeElement.CompareByDescendingDuration = function(a, b)
{
    return -1 * WebInspector.Resource.CompareByDuration(a.resource, b.resource);
}

WebInspector.ResourceSidebarTreeElement.CompareByDescendingLatency = function(a, b)
{
    return -1 * WebInspector.Resource.CompareByLatency(a.resource, b.resource);
}

WebInspector.ResourceSidebarTreeElement.CompareByDescendingSize = function(a, b)
{
    return -1 * WebInspector.Resource.CompareBySize(a.resource, b.resource);
}

WebInspector.ResourceSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype;

WebInspector.ResourceGraph = function(resource)
{
    this.resource = resource;

    this._graphElement = document.createElement("div");
    this._graphElement.className = "resources-graph-side";
    this._graphElement.addEventListener("mouseover", this.refreshLabelPositions.bind(this), false);

    if (resource.cached)
        this._graphElement.addStyleClass("resource-cached");

    this._barAreaElement = document.createElement("div");
    this._barAreaElement.className = "resources-graph-bar-area hidden";
    this._graphElement.appendChild(this._barAreaElement);

    this._barLeftElement = document.createElement("div");
    this._barLeftElement.className = "resources-graph-bar waiting";
    this._barAreaElement.appendChild(this._barLeftElement);

    this._barRightElement = document.createElement("div");
    this._barRightElement.className = "resources-graph-bar";
    this._barAreaElement.appendChild(this._barRightElement);

    this._labelLeftElement = document.createElement("div");
    this._labelLeftElement.className = "resources-graph-label waiting";
    this._barAreaElement.appendChild(this._labelLeftElement);

    this._labelRightElement = document.createElement("div");
    this._labelRightElement.className = "resources-graph-label";
    this._barAreaElement.appendChild(this._labelRightElement);

    this._graphElement.addStyleClass("resources-category-" + resource.category.name);
}

WebInspector.ResourceGraph.prototype = {
    get graphElement()
    {
        return this._graphElement;
    },

    refreshLabelPositions: function()
    {
        this._labelLeftElement.style.removeProperty("left");
        this._labelLeftElement.style.removeProperty("right");
        this._labelLeftElement.removeStyleClass("before");
        this._labelLeftElement.removeStyleClass("hidden");

        this._labelRightElement.style.removeProperty("left");
        this._labelRightElement.style.removeProperty("right");
        this._labelRightElement.removeStyleClass("after");
        this._labelRightElement.removeStyleClass("hidden");

        const labelPadding = 10;
        const rightBarWidth = (this._barRightElement.offsetWidth - labelPadding);
        const leftBarWidth = ((this._barLeftElement.offsetWidth - this._barRightElement.offsetWidth) - labelPadding);

        var labelBefore = (this._labelLeftElement.offsetWidth > leftBarWidth);
        var labelAfter = (this._labelRightElement.offsetWidth > rightBarWidth);

        if (labelBefore) {
            if ((this._graphElement.offsetWidth * (this._percentages.start / 100)) < (this._labelLeftElement.offsetWidth + 10))
                this._labelLeftElement.addStyleClass("hidden");
            this._labelLeftElement.style.setProperty("right", (100 - this._percentages.start) + "%");
            this._labelLeftElement.addStyleClass("before");
        } else {
            this._labelLeftElement.style.setProperty("left", this._percentages.start + "%");
            this._labelLeftElement.style.setProperty("right", (100 - this._percentages.middle) + "%");
        }

        if (labelAfter) {
            if ((this._graphElement.offsetWidth * ((100 - this._percentages.end) / 100)) < (this._labelRightElement.offsetWidth + 10))
                this._labelRightElement.addStyleClass("hidden");
            this._labelRightElement.style.setProperty("left", this._percentages.end + "%");
            this._labelRightElement.addStyleClass("after");
        } else {
            this._labelRightElement.style.setProperty("left", this._percentages.middle + "%");
            this._labelRightElement.style.setProperty("right", (100 - this._percentages.end) + "%");
        }
    },

    refresh: function(calculator)
    {
        var percentages = calculator.computeBarGraphPercentages(this.resource);
        var labels = calculator.computeBarGraphLabels(this.resource);

        this._percentages = percentages;

        this._barAreaElement.removeStyleClass("hidden");

        if (!this._graphElement.hasStyleClass("resources-category-" + this.resource.category.name)) {
            this._graphElement.removeMatchingStyleClasses("resources-category-\\w+");
            this._graphElement.addStyleClass("resources-category-" + this.resource.category.name);
        }

        this._barLeftElement.style.setProperty("left", percentages.start + "%");
        this._barLeftElement.style.setProperty("right", (100 - percentages.end) + "%");

        this._barRightElement.style.setProperty("left", percentages.middle + "%");
        this._barRightElement.style.setProperty("right", (100 - percentages.end) + "%");

        this._labelLeftElement.textContent = labels.left;
        this._labelRightElement.textContent = labels.right;

        var tooltip = (labels.tooltip || "");
        this._barLeftElement.title = tooltip;
        this._labelLeftElement.title = tooltip;
        this._labelRightElement.title = tooltip;
        this._barRightElement.title = tooltip;
    }
}

/* ScriptsPanel.js */

/*
 * Copyright (C) 2008 Apple Inc. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.ScriptsPanel = function()
{
    WebInspector.Panel.call(this);

    this.element.addStyleClass("scripts");

    this.topStatusBar = document.createElement("div");
    this.topStatusBar.className = "status-bar";
    this.topStatusBar.id = "scripts-status-bar";
    this.element.appendChild(this.topStatusBar);

    this.backButton = document.createElement("button");
    this.backButton.className = "status-bar-item";
    this.backButton.id = "scripts-back";
    this.backButton.title = WebInspector.UIString("Show the previous script resource.");
    this.backButton.disabled = true;
    this.backButton.appendChild(document.createElement("img"));
    this.backButton.addEventListener("click", this._goBack.bind(this), false);
    this.topStatusBar.appendChild(this.backButton);

    this.forwardButton = document.createElement("button");
    this.forwardButton.className = "status-bar-item";
    this.forwardButton.id = "scripts-forward";
    this.forwardButton.title = WebInspector.UIString("Show the next script resource.");
    this.forwardButton.disabled = true;
    this.forwardButton.appendChild(document.createElement("img"));
    this.forwardButton.addEventListener("click", this._goForward.bind(this), false);
    this.topStatusBar.appendChild(this.forwardButton);

    this.filesSelectElement = document.createElement("select");
    this.filesSelectElement.className = "status-bar-item";
    this.filesSelectElement.id = "scripts-files";
    this.filesSelectElement.addEventListener("change", this._changeVisibleFile.bind(this), false);
    this.filesSelectElement.handleKeyEvent = this.handleKeyEvent.bind(this);
    this.topStatusBar.appendChild(this.filesSelectElement);

    this.functionsSelectElement = document.createElement("select");
    this.functionsSelectElement.className = "status-bar-item";
    this.functionsSelectElement.id = "scripts-functions";

    // FIXME: append the functions select element to the top status bar when it is implemented.
    // this.topStatusBar.appendChild(this.functionsSelectElement);

    this.sidebarButtonsElement = document.createElement("div");
    this.sidebarButtonsElement.id = "scripts-sidebar-buttons";
    this.topStatusBar.appendChild(this.sidebarButtonsElement);

    this.pauseButton = document.createElement("button");
    this.pauseButton.className = "status-bar-item";
    this.pauseButton.id = "scripts-pause";
    this.pauseButton.title = WebInspector.UIString("Pause script execution.");
    this.pauseButton.disabled = true;
    this.pauseButton.appendChild(document.createElement("img"));
    this.pauseButton.addEventListener("click", this._togglePause.bind(this), false);
    this.sidebarButtonsElement.appendChild(this.pauseButton);

    this.stepOverButton = document.createElement("button");
    this.stepOverButton.className = "status-bar-item";
    this.stepOverButton.id = "scripts-step-over";
    this.stepOverButton.title = WebInspector.UIString("Step over next function call.");
    this.stepOverButton.disabled = true;
    this.stepOverButton.addEventListener("click", this._stepOverClicked.bind(this), false);
    this.stepOverButton.appendChild(document.createElement("img"));
    this.sidebarButtonsElement.appendChild(this.stepOverButton);

    this.stepIntoButton = document.createElement("button");
    this.stepIntoButton.className = "status-bar-item";
    this.stepIntoButton.id = "scripts-step-into";
    this.stepIntoButton.title = WebInspector.UIString("Step into next function call.");
    this.stepIntoButton.disabled = true;
    this.stepIntoButton.addEventListener("click", this._stepIntoClicked.bind(this), false);
    this.stepIntoButton.appendChild(document.createElement("img"));
    this.sidebarButtonsElement.appendChild(this.stepIntoButton);

    this.stepOutButton = document.createElement("button");
    this.stepOutButton.className = "status-bar-item";
    this.stepOutButton.id = "scripts-step-out";
    this.stepOutButton.title = WebInspector.UIString("Step out of current function.");
    this.stepOutButton.disabled = true;
    this.stepOutButton.addEventListener("click", this._stepOutClicked.bind(this), false);
    this.stepOutButton.appendChild(document.createElement("img"));
    this.sidebarButtonsElement.appendChild(this.stepOutButton);

    this.debuggerStatusElement = document.createElement("div");
    this.debuggerStatusElement.id = "scripts-debugger-status";
    this.sidebarButtonsElement.appendChild(this.debuggerStatusElement);

    this.viewsContainerElement = document.createElement("div");
    this.viewsContainerElement.id = "script-resource-views";

    this.sidebarElement = document.createElement("div");
    this.sidebarElement.id = "scripts-sidebar";

    this.sidebarResizeElement = document.createElement("div");
    this.sidebarResizeElement.className = "sidebar-resizer-vertical";
    this.sidebarResizeElement.addEventListener("mousedown", this._startSidebarResizeDrag.bind(this), false);

    this.sidebarResizeWidgetElement = document.createElement("div");
    this.sidebarResizeWidgetElement.id = "scripts-sidebar-resizer-widget";
    this.sidebarResizeWidgetElement.addEventListener("mousedown", this._startSidebarResizeDrag.bind(this), false);
    this.topStatusBar.appendChild(this.sidebarResizeWidgetElement);

    this.sidebarPanes = {};
    this.sidebarPanes.watchExpressions = new WebInspector.WatchExpressionsSidebarPane();
    this.sidebarPanes.callstack = new WebInspector.CallStackSidebarPane();
    this.sidebarPanes.scopechain = new WebInspector.ScopeChainSidebarPane();
    this.sidebarPanes.breakpoints = new WebInspector.BreakpointsSidebarPane();

    for (var pane in this.sidebarPanes)
        this.sidebarElement.appendChild(this.sidebarPanes[pane].element);

    this.sidebarPanes.callstack.expanded = true;
    this.sidebarPanes.callstack.addEventListener("call frame selected", this._callFrameSelected, this);

    this.sidebarPanes.scopechain.expanded = true;
    this.sidebarPanes.breakpoints.expanded = true;

    var panelEnablerHeading = WebInspector.UIString("You need to enable debugging before you can use the Scripts panel.");
    var panelEnablerDisclaimer = WebInspector.UIString("Enabling debugging will make scripts run slower.");
    var panelEnablerButton = WebInspector.UIString("Enable Debugging");

    this.panelEnablerView = new WebInspector.PanelEnablerView("scripts", panelEnablerHeading, panelEnablerDisclaimer, panelEnablerButton);
    this.panelEnablerView.addEventListener("enable clicked", this._enableDebugging, this);

    this.element.appendChild(this.panelEnablerView.element);
    this.element.appendChild(this.viewsContainerElement);
    this.element.appendChild(this.sidebarElement);
    this.element.appendChild(this.sidebarResizeElement);

    this.enableToggleButton = new WebInspector.StatusBarButton("", "enable-toggle-status-bar-item");
    this.enableToggleButton.addEventListener("click", this._toggleDebugging.bind(this), false);

    this.pauseOnExceptionButton = new WebInspector.StatusBarButton("", "scripts-pause-on-exceptions-status-bar-item");
    this.pauseOnExceptionButton.addEventListener("click", this._togglePauseOnExceptions.bind(this), false);

    this._breakpointsURLMap = {};

    this._shortcuts = {};

    var isMac = InspectorController.platform().indexOf("mac-") === 0;
    var platformSpecificModifier = isMac ? WebInspector.KeyboardShortcut.Modifiers.Meta : WebInspector.KeyboardShortcut.Modifiers.Ctrl;

    // Continue.
    var handler = this.pauseButton.click.bind(this.pauseButton);
    var shortcut = WebInspector.KeyboardShortcut.makeKey(WebInspector.KeyboardShortcut.KeyCodes.F8);
    this._shortcuts[shortcut] = handler;
    var shortcut = WebInspector.KeyboardShortcut.makeKey(WebInspector.KeyboardShortcut.KeyCodes.Slash, platformSpecificModifier);
    this._shortcuts[shortcut] = handler;

    // Step over.
    var handler = this.stepOverButton.click.bind(this.stepOverButton);
    var shortcut = WebInspector.KeyboardShortcut.makeKey(WebInspector.KeyboardShortcut.KeyCodes.F10);
    this._shortcuts[shortcut] = handler;
    var shortcut = WebInspector.KeyboardShortcut.makeKey(WebInspector.KeyboardShortcut.KeyCodes.SingleQuote, platformSpecificModifier);
    this._shortcuts[shortcut] = handler;

    // Step into.
    var handler = this.stepIntoButton.click.bind(this.stepIntoButton);
    var shortcut = WebInspector.KeyboardShortcut.makeKey(WebInspector.KeyboardShortcut.KeyCodes.F11);
    this._shortcuts[shortcut] = handler;
    var shortcut = WebInspector.KeyboardShortcut.makeKey(WebInspector.KeyboardShortcut.KeyCodes.Semicolon, platformSpecificModifier);
    this._shortcuts[shortcut] = handler;

    // Step out.
    var handler = this.stepOutButton.click.bind(this.stepOutButton);
    var shortcut = WebInspector.KeyboardShortcut.makeKey(WebInspector.KeyboardShortcut.KeyCodes.F11, WebInspector.KeyboardShortcut.Modifiers.Shift);
    this._shortcuts[shortcut] = handler;
    var shortcut = WebInspector.KeyboardShortcut.makeKey(WebInspector.KeyboardShortcut.KeyCodes.Semicolon, WebInspector.KeyboardShortcut.Modifiers.Shift, platformSpecificModifier);
    this._shortcuts[shortcut] = handler;

    this.reset();
}

WebInspector.ScriptsPanel.prototype = {
    toolbarItemClass: "scripts",

    get toolbarItemLabel()
    {
        return WebInspector.UIString("Scripts");
    },

    get statusBarItems()
    {
        return [this.enableToggleButton.element, this.pauseOnExceptionButton.element];
    },

    get paused()
    {
        return this._paused;
    },

    show: function()
    {
        WebInspector.Panel.prototype.show.call(this);
        this.sidebarResizeElement.style.right = (this.sidebarElement.offsetWidth - 3) + "px";

        if (this.visibleView) {
            if (this.visibleView instanceof WebInspector.ResourceView)
                this.visibleView.headersVisible = false;
            this.visibleView.show(this.viewsContainerElement);
        }

        // Hide any views that are visible that are not this panel's current visible view.
        // This can happen when a ResourceView is visible in the Resources panel then switched
        // to the this panel.
        for (var sourceID in this._sourceIDMap) {
            var scriptOrResource = this._sourceIDMap[sourceID];
            var view = this._sourceViewForScriptOrResource(scriptOrResource);
            if (!view || view === this.visibleView)
                continue;
            view.visible = false;
        }
        if (this._attachDebuggerWhenShown) {
            InspectorController.enableDebugger(false);
            delete this._attachDebuggerWhenShown;
        }
    },

    get searchableViews()
    {
        var views = [];

        const visibleView = this.visibleView;
        if (visibleView && visibleView.performSearch) {
            visibleView.alreadySearching = true;
            views.push(visibleView);
        }

        for (var sourceID in this._sourceIDMap) {
            var scriptOrResource = this._sourceIDMap[sourceID];
            var view = this._sourceViewForScriptOrResource(scriptOrResource);
            if (!view || !view.performSearch || view.alreadySearching)
                continue;

            view.alreadySearching = true;
            views.push(view);
        }

        for (var i = 0; i < views.length; ++i)
            delete views[i].alreadySearching;

        return views;
    },

    addScript: function(sourceID, sourceURL, source, startingLine, errorLine, errorMessage)
    {
        var script = new WebInspector.Script(sourceID, sourceURL, source, startingLine, errorLine, errorMessage);

        if (sourceURL in WebInspector.resourceURLMap) {
            var resource = WebInspector.resourceURLMap[sourceURL];
            resource.addScript(script);
        }

        sourceURL = script.sourceURL;

        if (sourceID)
            this._sourceIDMap[sourceID] = (resource || script);

        if (sourceURL in this._breakpointsURLMap && sourceID) {
            var breakpoints = this._breakpointsURLMap[sourceURL];
            var breakpointsLength = breakpoints.length;
            for (var i = 0; i < breakpointsLength; ++i) {
                var breakpoint = breakpoints[i];

                if (startingLine <= breakpoint.line) {
                    // remove and add the breakpoint, to clean up things like the sidebar
                    this.removeBreakpoint(breakpoint);
                    breakpoint.sourceID = sourceID;
                    this.addBreakpoint(breakpoint);
                    
                    if (breakpoint.enabled)
                        InspectorController.addBreakpoint(breakpoint.sourceID, breakpoint.line, breakpoint.condition);
                }
            }
        }

        this._addScriptToFilesMenu(script);
    },

    scriptOrResourceForID: function(id)
    {
        return this._sourceIDMap[id];
    },

    addBreakpoint: function(breakpoint)
    {
        this.sidebarPanes.breakpoints.addBreakpoint(breakpoint);

        var sourceFrame;
        if (breakpoint.url) {
            if (!(breakpoint.url in this._breakpointsURLMap))
                this._breakpointsURLMap[breakpoint.url] = [];
            this._breakpointsURLMap[breakpoint.url].unshift(breakpoint);

            if (breakpoint.url in WebInspector.resourceURLMap) {
                var resource = WebInspector.resourceURLMap[breakpoint.url];
                sourceFrame = this._sourceFrameForScriptOrResource(resource);
            }
        }

        if (breakpoint.sourceID && !sourceFrame) {
            var object = this._sourceIDMap[breakpoint.sourceID]
            sourceFrame = this._sourceFrameForScriptOrResource(object);
        }

        if (sourceFrame)
            sourceFrame.addBreakpoint(breakpoint);
    },

    removeBreakpoint: function(breakpoint)
    {
        this.sidebarPanes.breakpoints.removeBreakpoint(breakpoint);

        var sourceFrame;
        if (breakpoint.url && breakpoint.url in this._breakpointsURLMap) {
            var breakpoints = this._breakpointsURLMap[breakpoint.url];
            breakpoints.remove(breakpoint);
            if (!breakpoints.length)
                delete this._breakpointsURLMap[breakpoint.url];

            if (breakpoint.url in WebInspector.resourceURLMap) {
                var resource = WebInspector.resourceURLMap[breakpoint.url];
                sourceFrame = this._sourceFrameForScriptOrResource(resource);
            }
        }

        if (breakpoint.sourceID && !sourceFrame) {
            var object = this._sourceIDMap[breakpoint.sourceID]
            sourceFrame = this._sourceFrameForScriptOrResource(object);
        }

        if (sourceFrame)
            sourceFrame.removeBreakpoint(breakpoint);
    },

    selectedCallFrameId: function()
    {
        var selectedCallFrame = this.sidebarPanes.callstack.selectedCallFrame;
        if (!selectedCallFrame)
            return null;
        return selectedCallFrame.id;
    },

    evaluateInSelectedCallFrame: function(code, updateInterface, objectGroup, callback)
    {
        var selectedCallFrame = this.sidebarPanes.callstack.selectedCallFrame;
        if (!this._paused || !selectedCallFrame)
            return;

        if (typeof updateInterface === "undefined")
            updateInterface = true;

        var self = this;
        function updatingCallbackWrapper(result, exception)
        {
            callback(result, exception);
            if (updateInterface)
                self.sidebarPanes.scopechain.update(selectedCallFrame);
        }
        this.doEvalInCallFrame(selectedCallFrame, code, objectGroup, updatingCallbackWrapper);
    },

    doEvalInCallFrame: function(callFrame, code, objectGroup, callback)
    {
        function evalCallback(result)
        {
            if (result)
                callback(result.value, result.isException);
        }
        InjectedScriptAccess.evaluateInCallFrame(callFrame.id, code, objectGroup, evalCallback);
    },

    debuggerPaused: function(callFrames)
    {
        this._paused = true;
        this._waitingToPause = false;
        this._stepping = false;

        this._updateDebuggerButtons();

        this.sidebarPanes.callstack.update(callFrames, this._sourceIDMap);
        this.sidebarPanes.callstack.selectedCallFrame = callFrames[0];

        WebInspector.currentPanel = this;
        window.focus();
    },

    debuggerResumed: function()
    {
        this._paused = false;
        this._waitingToPause = false;
        this._stepping = false;

        this._clearInterface();
    },

    attachDebuggerWhenShown: function()
    {
        if (this.element.parentElement) {
            InspectorController.enableDebugger(false);
        } else {
            this._attachDebuggerWhenShown = true;
        }
    },

    debuggerWasEnabled: function()
    {
        this.reset();
    },

    debuggerWasDisabled: function()
    {
        this.reset();
    },

    reset: function()
    {
        this.visibleView = null;

        delete this.currentQuery;
        this.searchCanceled();

        if (!InspectorController.debuggerEnabled()) {
            this._paused = false;
            this._waitingToPause = false;
            this._stepping = false;
        }

        this._clearInterface();

        this._backForwardList = [];
        this._currentBackForwardIndex = -1;
        this._updateBackAndForwardButtons();

        this._scriptsForURLsInFilesSelect = {};
        this.filesSelectElement.removeChildren();
        this.functionsSelectElement.removeChildren();
        this.viewsContainerElement.removeChildren();

        if (this._sourceIDMap) {
            for (var sourceID in this._sourceIDMap) {
                var object = this._sourceIDMap[sourceID];
                if (object instanceof WebInspector.Resource)
                    object.removeAllScripts();
            }
        }

        this._sourceIDMap = {};
        
        this.sidebarPanes.watchExpressions.refreshExpressions();
    },

    get visibleView()
    {
        return this._visibleView;
    },

    set visibleView(x)
    {
        if (this._visibleView === x)
            return;

        if (this._visibleView)
            this._visibleView.hide();

        this._visibleView = x;

        if (x)
            x.show(this.viewsContainerElement);
    },

    canShowResource: function(resource)
    {
        return resource && resource.scripts.length && InspectorController.debuggerEnabled();
    },

    showScript: function(script, line)
    {
        this._showScriptOrResource(script, line, true);
    },

    showResource: function(resource, line)
    {
        this._showScriptOrResource(resource, line, true);
    },

    showView: function(view)
    {
        if (!view)
            return;
        this._showScriptOrResource((view.resource || view.script));
    },

    handleKeyEvent: function(event)
    {
        var shortcut = WebInspector.KeyboardShortcut.makeKeyFromEvent(event);
        var handler = this._shortcuts[shortcut];
        if (handler) {
            handler(event);
            event.preventDefault();
            event.handled = true;
        } else {
            this.sidebarPanes.callstack.handleKeyEvent(event);
        }
    },

    scriptViewForScript: function(script)
    {
        if (!script)
            return null;
        if (!script._scriptView)
            script._scriptView = new WebInspector.ScriptView(script);
        return script._scriptView;
    },

    sourceFrameForScript: function(script)
    {
        var view = this.scriptViewForScript(script);
        if (!view)
            return null;

        // Setting up the source frame requires that we be attached.
        if (!this.element.parentNode)
            this.attach();

        view.setupSourceFrameIfNeeded();
        return view.sourceFrame;
    },

    _sourceViewForScriptOrResource: function(scriptOrResource)
    {
        if (scriptOrResource instanceof WebInspector.Resource) {
            if (!WebInspector.panels.resources)
                return null;
            return WebInspector.panels.resources.resourceViewForResource(scriptOrResource);
        }
        if (scriptOrResource instanceof WebInspector.Script)
            return this.scriptViewForScript(scriptOrResource);
    },

    _sourceFrameForScriptOrResource: function(scriptOrResource)
    {
        if (scriptOrResource instanceof WebInspector.Resource) {
            if (!WebInspector.panels.resources)
                return null;
            return WebInspector.panels.resources.sourceFrameForResource(scriptOrResource);
        }
        if (scriptOrResource instanceof WebInspector.Script)
            return this.sourceFrameForScript(scriptOrResource);
    },

    _showScriptOrResource: function(scriptOrResource, line, shouldHighlightLine, fromBackForwardAction)
    {
        if (!scriptOrResource)
            return;

        var view;
        if (scriptOrResource instanceof WebInspector.Resource) {
            if (!WebInspector.panels.resources)
                return null;
            view = WebInspector.panels.resources.resourceViewForResource(scriptOrResource);
            view.headersVisible = false;

            if (scriptOrResource.url in this._breakpointsURLMap) {
                var sourceFrame = this._sourceFrameForScriptOrResource(scriptOrResource);
                if (sourceFrame && !sourceFrame.breakpoints.length) {
                    var breakpoints = this._breakpointsURLMap[scriptOrResource.url];
                    var breakpointsLength = breakpoints.length;
                    for (var i = 0; i < breakpointsLength; ++i)
                        sourceFrame.addBreakpoint(breakpoints[i]);
                }
            }
        } else if (scriptOrResource instanceof WebInspector.Script)
            view = this.scriptViewForScript(scriptOrResource);

        if (!view)
            return;

        if (!fromBackForwardAction) {
            var oldIndex = this._currentBackForwardIndex;
            if (oldIndex >= 0)
                this._backForwardList.splice(oldIndex + 1, this._backForwardList.length - oldIndex);

            // Check for a previous entry of the same object in _backForwardList.
            // If one is found, remove it and update _currentBackForwardIndex to match.
            var previousEntryIndex = this._backForwardList.indexOf(scriptOrResource);
            if (previousEntryIndex !== -1) {
                this._backForwardList.splice(previousEntryIndex, 1);
                --this._currentBackForwardIndex;
            }

            this._backForwardList.push(scriptOrResource);
            ++this._currentBackForwardIndex;

            this._updateBackAndForwardButtons();
        }

        this.visibleView = view;

        if (line) {
            if (view.revealLine)
                view.revealLine(line);
            if (view.highlightLine && shouldHighlightLine)
                view.highlightLine(line);
        }

        var option;
        if (scriptOrResource instanceof WebInspector.Script) {
            option = scriptOrResource.filesSelectOption;

            // hasn't been added yet - happens for stepping in evals,
            // so use the force option to force the script into the menu.
            if (!option) {
                this._addScriptToFilesMenu(scriptOrResource, {force: true});
                option = scriptOrResource.filesSelectOption;
            }

            console.assert(option);
        } else {
            var url = scriptOrResource.url;
            var script = this._scriptsForURLsInFilesSelect[url];
            if (script)
               option = script.filesSelectOption;
        }

        if (option)
            this.filesSelectElement.selectedIndex = option.index;
    },

    _addScriptToFilesMenu: function(script, options)
    {
        var force = options && options.force;

        if (!script.sourceURL && !force)
            return;

        if (script.resource && this._scriptsForURLsInFilesSelect[script.sourceURL])
            return;

        this._scriptsForURLsInFilesSelect[script.sourceURL] = script;

        var select = this.filesSelectElement;

        var option = document.createElement("option");
        option.representedObject = (script.resource || script);
        option.text = (script.sourceURL ? WebInspector.displayNameForURL(script.sourceURL) : WebInspector.UIString("(program)"));

        function optionCompare(a, b)
        {
            var aTitle = a.text.toLowerCase();
            var bTitle = b.text.toLowerCase();
            if (aTitle < bTitle)
                return -1;
            else if (aTitle > bTitle)
                return 1;

            var aSourceID = a.representedObject.sourceID;
            var bSourceID = b.representedObject.sourceID;
            if (aSourceID < bSourceID)
                return -1;
            else if (aSourceID > bSourceID)
                return 1;
            return 0;
        }

        var insertionIndex = insertionIndexForObjectInListSortedByFunction(option, select.childNodes, optionCompare);
        if (insertionIndex < 0)
            select.appendChild(option);
        else
            select.insertBefore(option, select.childNodes.item(insertionIndex));

        script.filesSelectOption = option;

        // Call _showScriptOrResource if the option we just appended ended up being selected.
        // This will happen for the first item added to the menu.
        if (select.options[select.selectedIndex] === option)
            this._showScriptOrResource(option.representedObject);
    },

    _clearCurrentExecutionLine: function()
    {
        if (this._executionSourceFrame)
            this._executionSourceFrame.executionLine = 0;
        delete this._executionSourceFrame;
    },

    _callFrameSelected: function()
    {
        this._clearCurrentExecutionLine();

        var callStackPane = this.sidebarPanes.callstack;
        var currentFrame = callStackPane.selectedCallFrame;
        if (!currentFrame)
            return;

        this.sidebarPanes.scopechain.update(currentFrame);
        this.sidebarPanes.watchExpressions.refreshExpressions();

        var scriptOrResource = this._sourceIDMap[currentFrame.sourceID];
        this._showScriptOrResource(scriptOrResource, currentFrame.line);

        this._executionSourceFrame = this._sourceFrameForScriptOrResource(scriptOrResource);
        if (this._executionSourceFrame)
            this._executionSourceFrame.executionLine = currentFrame.line;
    },

    _changeVisibleFile: function(event)
    {
        var select = this.filesSelectElement;
        this._showScriptOrResource(select.options[select.selectedIndex].representedObject);
    },

    _startSidebarResizeDrag: function(event)
    {
        WebInspector.elementDragStart(this.sidebarElement, this._sidebarResizeDrag.bind(this), this._endSidebarResizeDrag.bind(this), event, "col-resize");

        if (event.target === this.sidebarResizeWidgetElement)
            this._dragOffset = (event.target.offsetWidth - (event.pageX - event.target.totalOffsetLeft));
        else
            this._dragOffset = 0;
    },

    _endSidebarResizeDrag: function(event)
    {
        WebInspector.elementDragEnd(event);

        delete this._dragOffset;
    },

    _sidebarResizeDrag: function(event)
    {
        var x = event.pageX + this._dragOffset;
        var newWidth = Number.constrain(window.innerWidth - x, Preferences.minScriptsSidebarWidth, window.innerWidth * 0.66);

        this.sidebarElement.style.width = newWidth + "px";
        this.sidebarButtonsElement.style.width = newWidth + "px";
        this.viewsContainerElement.style.right = newWidth + "px";
        this.sidebarResizeWidgetElement.style.right = newWidth + "px";
        this.sidebarResizeElement.style.right = (newWidth - 3) + "px";

        event.preventDefault();
    },

    _updatePauseOnExceptionsButton: function()
    {
        if (InspectorController.pauseOnExceptions()) {
            this.pauseOnExceptionButton.title = WebInspector.UIString("Don't pause on exceptions.");
            this.pauseOnExceptionButton.toggled = true;
        } else {
            this.pauseOnExceptionButton.title = WebInspector.UIString("Pause on exceptions.");
            this.pauseOnExceptionButton.toggled = false;
        }
    },

    _updateDebuggerButtons: function()
    {
        if (InspectorController.debuggerEnabled()) {
            this.enableToggleButton.title = WebInspector.UIString("Debugging enabled. Click to disable.");
            this.enableToggleButton.toggled = true;
            this.pauseOnExceptionButton.visible = true;
            this.panelEnablerView.visible = false;
        } else {
            this.enableToggleButton.title = WebInspector.UIString("Debugging disabled. Click to enable.");
            this.enableToggleButton.toggled = false;
            this.pauseOnExceptionButton.visible = false;
            this.panelEnablerView.visible = true;
        }

        this._updatePauseOnExceptionsButton();

        if (this._paused) {
            this.pauseButton.addStyleClass("paused");

            this.pauseButton.disabled = false;
            this.stepOverButton.disabled = false;
            this.stepIntoButton.disabled = false;
            this.stepOutButton.disabled = false;

            this.debuggerStatusElement.textContent = WebInspector.UIString("Paused");
        } else {
            this.pauseButton.removeStyleClass("paused");

            this.pauseButton.disabled = this._waitingToPause;
            this.stepOverButton.disabled = true;
            this.stepIntoButton.disabled = true;
            this.stepOutButton.disabled = true;

            if (this._waitingToPause)
                this.debuggerStatusElement.textContent = WebInspector.UIString("Pausing");
            else if (this._stepping)
                this.debuggerStatusElement.textContent = WebInspector.UIString("Stepping");
            else
                this.debuggerStatusElement.textContent = "";
        }
    },

    _updateBackAndForwardButtons: function()
    {
        this.backButton.disabled = this._currentBackForwardIndex <= 0;
        this.forwardButton.disabled = this._currentBackForwardIndex >= (this._backForwardList.length - 1);
    },

    _clearInterface: function()
    {
        this.sidebarPanes.callstack.update(null);
        this.sidebarPanes.scopechain.update(null);

        this._clearCurrentExecutionLine();
        this._updateDebuggerButtons();
    },

    _goBack: function()
    {
        if (this._currentBackForwardIndex <= 0) {
            console.error("Can't go back from index " + this._currentBackForwardIndex);
            return;
        }

        this._showScriptOrResource(this._backForwardList[--this._currentBackForwardIndex], null, false, true);
        this._updateBackAndForwardButtons();
    },

    _goForward: function()
    {
        if (this._currentBackForwardIndex >= this._backForwardList.length - 1) {
            console.error("Can't go forward from index " + this._currentBackForwardIndex);
            return;
        }

        this._showScriptOrResource(this._backForwardList[++this._currentBackForwardIndex], null, false, true);
        this._updateBackAndForwardButtons();
    },

    _enableDebugging: function()
    {
        if (InspectorController.debuggerEnabled())
            return;
        this._toggleDebugging(this.panelEnablerView.alwaysEnabled);
    },

    _toggleDebugging: function(optionalAlways)
    {
        this._paused = false;
        this._waitingToPause = false;
        this._stepping = false;

        if (InspectorController.debuggerEnabled())
            InspectorController.disableDebugger(true);
        else
            InspectorController.enableDebugger(!!optionalAlways);
    },

    _togglePauseOnExceptions: function()
    {
        InspectorController.setPauseOnExceptions(!InspectorController.pauseOnExceptions());
        this._updatePauseOnExceptionsButton();
    },

    _togglePause: function()
    {
        if (this._paused) {
            this._paused = false;
            this._waitingToPause = false;
            InspectorController.resumeDebugger();
        } else {
            this._stepping = false;
            this._waitingToPause = true;
            InspectorController.pauseInDebugger();
        }

        this._clearInterface();
    },

    _stepOverClicked: function()
    {
        this._paused = false;
        this._stepping = true;

        this._clearInterface();

        InspectorController.stepOverStatementInDebugger();
    },

    _stepIntoClicked: function()
    {
        this._paused = false;
        this._stepping = true;

        this._clearInterface();

        InspectorController.stepIntoStatementInDebugger();
    },

    _stepOutClicked: function()
    {
        this._paused = false;
        this._stepping = true;

        this._clearInterface();

        InspectorController.stepOutOfFunctionInDebugger();
    }
}

WebInspector.ScriptsPanel.prototype.__proto__ = WebInspector.Panel.prototype;

/* StoragePanel.js */

/*
 * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
 * Copyright (C) 2009 Joseph Pecoraro
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.StoragePanel = function(database)
{
    WebInspector.Panel.call(this);

    this.createSidebar();

    this.databasesListTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("DATABASES"), {}, true);
    this.sidebarTree.appendChild(this.databasesListTreeElement);
    this.databasesListTreeElement.expand();

    this.localStorageListTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("LOCAL STORAGE"), {}, true);
    this.sidebarTree.appendChild(this.localStorageListTreeElement);
    this.localStorageListTreeElement.expand();

    this.sessionStorageListTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("SESSION STORAGE"), {}, true);
    this.sidebarTree.appendChild(this.sessionStorageListTreeElement);
    this.sessionStorageListTreeElement.expand();

    this.cookieListTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("COOKIES"), {}, true);
    this.sidebarTree.appendChild(this.cookieListTreeElement);
    this.cookieListTreeElement.expand();

    this.storageViews = document.createElement("div");
    this.storageViews.id = "storage-views";
    this.element.appendChild(this.storageViews);

    this.storageViewStatusBarItemsContainer = document.createElement("div");
    this.storageViewStatusBarItemsContainer.id = "storage-view-status-bar-items";

    this.reset();
}

WebInspector.StoragePanel.prototype = {
    toolbarItemClass: "storage",

    get toolbarItemLabel()
    {
        return WebInspector.UIString("Storage");
    },

    get statusBarItems()
    {
        return [this.storageViewStatusBarItemsContainer];
    },

    reset: function()
    {
        if (this._databases) {
            var databasesLength = this._databases.length;
            for (var i = 0; i < databasesLength; ++i) {
                var database = this._databases[i];

                delete database._tableViews;
                delete database._queryView;
            }
        }

        this._databases = [];

        if (this._domStorage) {
            var domStorageLength = this._domStorage.length;
            for (var i = 0; i < domStorageLength; ++i) {
                var domStorage = this._domStorage[i];

                delete domStorage._domStorageView;
            }
        }

        this._domStorage = [];

        this._cookieDomains = {};
        this._cookieViews = {};

        this.databasesListTreeElement.removeChildren();
        this.localStorageListTreeElement.removeChildren();
        this.sessionStorageListTreeElement.removeChildren();
        this.cookieListTreeElement.removeChildren();

        this.storageViews.removeChildren();        

        this.storageViewStatusBarItemsContainer.removeChildren();
        
        if (this.sidebarTree.selectedTreeElement)
            this.sidebarTree.selectedTreeElement.deselect();
    },

    addDatabase: function(database)
    {
        this._databases.push(database);

        var databaseTreeElement = new WebInspector.DatabaseSidebarTreeElement(database);
        database._databasesTreeElement = databaseTreeElement;
        this.databasesListTreeElement.appendChild(databaseTreeElement);
    },
    
    addCookieDomain: function(domain)
    {
        // Eliminate duplicate domains from the list.
        if (typeof this._cookieDomains[domain] !== "undefined")
            return;

        var cookieDomainTreeElement = new WebInspector.CookieSidebarTreeElement(domain);
        this.cookieListTreeElement.appendChild(cookieDomainTreeElement);
        this._cookieDomains[domain] = true;
    },

    addDOMStorage: function(domStorage)
    {
        this._domStorage.push(domStorage);
        var domStorageTreeElement = new WebInspector.DOMStorageSidebarTreeElement(domStorage, (domStorage.isLocalStorage ? "local-storage" : "session-storage"));
        domStorage._domStorageTreeElement = domStorageTreeElement;
        if (domStorage.isLocalStorage)
            this.localStorageListTreeElement.appendChild(domStorageTreeElement);
        else
            this.sessionStorageListTreeElement.appendChild(domStorageTreeElement);
    },

    selectDatabase: function(databaseId)
    {
        var database;
        for (var i = 0, len = this._databases.length; i < len; ++i) {
            database = this._databases[i];
            if (database.id === databaseId) {
                this.showDatabase(database);
                database._databasesTreeElement.select();
                return;
            }
        }
    },

    selectDOMStorage: function(storageId)
    {
        var domStorage = this._domStorageForId(storageId);
        if (domStorage) {
            this.showDOMStorage(domStorage);
            domStorage._domStorageTreeElement.select();
        }
    },

    showDatabase: function(database, tableName)
    {
        if (!database)
            return;

        if (this.visibleView)
            this.visibleView.hide();

        var view;
        if (tableName) {
            if (!("_tableViews" in database))
                database._tableViews = {};
            view = database._tableViews[tableName];
            if (!view) {
                view = new WebInspector.DatabaseTableView(database, tableName);
                database._tableViews[tableName] = view;
            }
        } else {
            view = database._queryView;
            if (!view) {
                view = new WebInspector.DatabaseQueryView(database);
                database._queryView = view;
            }
        }

        view.show(this.storageViews);

        this.visibleView = view;

        this.storageViewStatusBarItemsContainer.removeChildren();
        var statusBarItems = view.statusBarItems || [];
        for (var i = 0; i < statusBarItems.length; ++i)
            this.storageViewStatusBarItemsContainer.appendChild(statusBarItems[i].element);
    },

    showDOMStorage: function(domStorage)
    {
        if (!domStorage)
            return;

        if (this.visibleView)
            this.visibleView.hide();

        var view;
        view = domStorage._domStorageView;
        if (!view) {
            view = new WebInspector.DOMStorageItemsView(domStorage);
            domStorage._domStorageView = view;
        }

        view.show(this.storageViews);

        this.visibleView = view;

        this.storageViewStatusBarItemsContainer.removeChildren();
        var statusBarItems = view.statusBarItems;
        for (var i = 0; i < statusBarItems.length; ++i)
            this.storageViewStatusBarItemsContainer.appendChild(statusBarItems[i]);
    },

    showCookies: function(cookieDomain)
    {
        if (this.visibleView)
            this.visibleView.hide();

        var view = this._cookieViews[cookieDomain];
        if (!view) {
            view = new WebInspector.CookieItemsView(cookieDomain);
            this._cookieViews[cookieDomain] = view;
        }

        view.show(this.storageViews);

        this.visibleView = view;

        this.storageViewStatusBarItemsContainer.removeChildren();
        var statusBarItems = view.statusBarItems;
        for (var i = 0; i < statusBarItems.length; ++i)
            this.storageViewStatusBarItemsContainer.appendChild(statusBarItems[i]);
    },

    closeVisibleView: function()
    {
        if (this.visibleView)
            this.visibleView.hide();
        delete this.visibleView;
    },

    updateDatabaseTables: function(database)
    {
        if (!database || !database._databasesTreeElement)
            return;

        database._databasesTreeElement.shouldRefreshChildren = true;

        if (!("_tableViews" in database))
            return;

        var tableNamesHash = {};
        var self = this;
        function tableNamesCallback(tableNames)
        {
            var tableNamesLength = tableNames.length;
            for (var i = 0; i < tableNamesLength; ++i)
                tableNamesHash[tableNames[i]] = true;

            for (var tableName in database._tableViews) {
                if (!(tableName in tableNamesHash)) {
                    if (self.visibleView === database._tableViews[tableName])
                        self.closeVisibleView();
                    delete database._tableViews[tableName];
                }
            }
        }
        database.getTableNames(tableNamesCallback);
    },

    dataGridForResult: function(rows)
    {
        if (!rows.length)
            return null;

        var columns = {};
        var numColumns = 0;

        for (var columnIdentifier in rows[0]) {
            var column = {};
            column.width = columnIdentifier.length;
            column.title = columnIdentifier;

            columns[columnIdentifier] = column;
            ++numColumns;
        }

        var nodes = [];
        var length = rows.length;
        for (var i = 0; i < length; ++i) {
            var data = {};

            var row = rows[i];
            for (var columnIdentifier in row) {
                var text = row[columnIdentifier];
                data[columnIdentifier] = text;
                if (text.length > columns[columnIdentifier].width)
                    columns[columnIdentifier].width = text.length;
            }

            var node = new WebInspector.DataGridNode(data, false);
            node.selectable = false;
            nodes.push(node);
        }

        var totalColumnWidths = 0;
        for (var columnIdentifier in columns)
            totalColumnWidths += columns[columnIdentifier].width;

        // Calculate the percentage width for the columns.
        const minimumPrecent = Math.min(5, Math.floor(100/numColumns));
        var recoupPercent = 0;
        for (var columnIdentifier in columns) {
            var width = columns[columnIdentifier].width;
            width = Math.round((width / totalColumnWidths) * 100);
            if (width < minimumPrecent) {
                recoupPercent += (minimumPrecent - width);
                width = minimumPrecent;
            }

            columns[columnIdentifier].width = width;
        }

        // Enforce the minimum percentage width.
        while (recoupPercent > 0) {
            for (var columnIdentifier in columns) {
                if (columns[columnIdentifier].width > minimumPrecent) {
                    --columns[columnIdentifier].width;
                    --recoupPercent;
                    if (!recoupPercent)
                        break;
                }
            }
        }

        // Change the width property to a string suitable for a style width.
        for (var columnIdentifier in columns)
            columns[columnIdentifier].width += "%";

        var dataGrid = new WebInspector.DataGrid(columns);
        var length = nodes.length;
        for (var i = 0; i < length; ++i)
            dataGrid.appendChild(nodes[i]);

        return dataGrid;
    },

    resize: function()
    {
        var visibleView = this.visibleView;
        if (visibleView && "resize" in visibleView)
            visibleView.resize();
    },

    updateDOMStorage: function(storageId)
    {
        var domStorage = this._domStorageForId(storageId);
        if (!domStorage)
            return;

        var view = domStorage._domStorageView;
        if (this.visibleView && view === this.visibleView)
            domStorage._domStorageView.update();
    },

    _domStorageForId: function(storageId)
    {
        if (!this._domStorage)
            return null;
        var domStorageLength = this._domStorage.length;
        for (var i = 0; i < domStorageLength; ++i) {
            var domStorage = this._domStorage[i];
            if (domStorage.id == storageId)
                return domStorage;
        }
        return null;
    },

    updateMainViewWidth: function(width)
    {
        this.storageViews.style.left = width + "px";
        this.storageViewStatusBarItemsContainer.style.left = width + "px";
    }
}

WebInspector.StoragePanel.prototype.__proto__ = WebInspector.Panel.prototype;

WebInspector.DatabaseSidebarTreeElement = function(database)
{
    this.database = database;

    WebInspector.SidebarTreeElement.call(this, "database-sidebar-tree-item", "", "", database, true);

    this.refreshTitles();
}

WebInspector.DatabaseSidebarTreeElement.prototype = {
    onselect: function()
    {
        WebInspector.panels.storage.showDatabase(this.database);
    },

    oncollapse: function()
    {
        // Request a refresh after every collapse so the next
        // expand will have an updated table list.
        this.shouldRefreshChildren = true;
    },

    onpopulate: function()
    {
        this.removeChildren();

        var self = this;
        function tableNamesCallback(tableNames)
        {
            var tableNamesLength = tableNames.length;
            for (var i = 0; i < tableNamesLength; ++i)
                self.appendChild(new WebInspector.SidebarDatabaseTableTreeElement(self.database, tableNames[i]));
        }
        this.database.getTableNames(tableNamesCallback);
    },

    get mainTitle()
    {
        return this.database.name;
    },

    set mainTitle(x)
    {
        // Do nothing.
    },

    get subtitle()
    {
        return this.database.displayDomain;
    },

    set subtitle(x)
    {
        // Do nothing.
    }
}

WebInspector.DatabaseSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype;

WebInspector.SidebarDatabaseTableTreeElement = function(database, tableName)
{
    this.database = database;
    this.tableName = tableName;

    WebInspector.SidebarTreeElement.call(this, "database-table-sidebar-tree-item small", tableName, "", null, false);
}

WebInspector.SidebarDatabaseTableTreeElement.prototype = {
    onselect: function()
    {
        WebInspector.panels.storage.showDatabase(this.database, this.tableName);
    }
}

WebInspector.SidebarDatabaseTableTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype;

WebInspector.DOMStorageSidebarTreeElement = function(domStorage, className)
{

    this.domStorage = domStorage;

    WebInspector.SidebarTreeElement.call(this, "domstorage-sidebar-tree-item " + className, domStorage, "", null, false);

    this.refreshTitles();
}

WebInspector.DOMStorageSidebarTreeElement.prototype = {
    onselect: function()
    {
        WebInspector.panels.storage.showDOMStorage(this.domStorage);
    },

    get mainTitle()
    {
        return this.domStorage.domain ? this.domStorage.domain : WebInspector.UIString("Local Files");
    },

    set mainTitle(x)
    {
        // Do nothing.
    },

    get subtitle()
    {
        return ""; //this.database.displayDomain;
    },

    set subtitle(x)
    {
        // Do nothing.
    }
}

WebInspector.DOMStorageSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype;

WebInspector.CookieSidebarTreeElement = function(cookieDomain)
{
    WebInspector.SidebarTreeElement.call(this, "cookie-sidebar-tree-item", cookieDomain, "", null, false);
    this._cookieDomain = cookieDomain;

    this.refreshTitles();
}

WebInspector.CookieSidebarTreeElement.prototype = {
    onselect: function()
    {
        WebInspector.panels.storage.showCookies(this._cookieDomain);
    },
    
    get mainTitle()
    {
        return this._cookieDomain ? this._cookieDomain : WebInspector.UIString("Local Files");
    },

    set mainTitle(x)
    {
        // Do nothing.
    },

    get subtitle()
    {
        return "";
    },

    set subtitle(x)
    {
        // Do nothing.
    }
}

WebInspector.CookieSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype;

/* ProfilesPanel.js */

/*
 * Copyright (C) 2008 Apple Inc. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

const UserInitiatedProfileName = "org.webkit.profiles.user-initiated";

WebInspector.ProfileType = function(id, name)
{
    this._id = id;
    this._name = name;
}

WebInspector.ProfileType.URLRegExp = /webkit-profile:\/\/(.+)\/(.+)#([0-9]+)/;

WebInspector.ProfileType.prototype = {
    get buttonTooltip()
    {
        return "";
    },

    get buttonStyle()
    {
        return undefined;
    },

    get buttonCaption()
    {
        return this.name;
    },

    get id()
    {
        return this._id;
    },

    get name()
    {
        return this._name;
    },

    buttonClicked: function()
    {
    },

    viewForProfile: function(profile)
    {
        if (!profile._profileView)
            profile._profileView = this.createView(profile);
        return profile._profileView;
    },

    // Must be implemented by subclasses.
    createView: function(profile)
    {
        throw new Error("Needs implemented.");
    },

    // Must be implemented by subclasses.
    createSidebarTreeElementForProfile: function(profile)
    {
        throw new Error("Needs implemented.");
    }
}

WebInspector.ProfilesPanel = function()
{
    WebInspector.Panel.call(this);

    this.createSidebar();

    this.element.addStyleClass("profiles");
    this._profileTypesByIdMap = {};
    this._profileTypeButtonsByIdMap = {};

    var panelEnablerHeading = WebInspector.UIString("You need to enable profiling before you can use the Profiles panel.");
    var panelEnablerDisclaimer = WebInspector.UIString("Enabling profiling will make scripts run slower.");
    var panelEnablerButton = WebInspector.UIString("Enable Profiling");
    this.panelEnablerView = new WebInspector.PanelEnablerView("profiles", panelEnablerHeading, panelEnablerDisclaimer, panelEnablerButton);
    this.panelEnablerView.addEventListener("enable clicked", this._enableProfiling, this);

    this.element.appendChild(this.panelEnablerView.element);

    this.profileViews = document.createElement("div");
    this.profileViews.id = "profile-views";
    this.element.appendChild(this.profileViews);

    this.enableToggleButton = new WebInspector.StatusBarButton("", "enable-toggle-status-bar-item");
    this.enableToggleButton.addEventListener("click", this._toggleProfiling.bind(this), false);

    this.profileViewStatusBarItemsContainer = document.createElement("div");
    this.profileViewStatusBarItemsContainer.id = "profile-view-status-bar-items";

    this._profiles = [];
    this.reset();
}

WebInspector.ProfilesPanel.prototype = {
    toolbarItemClass: "profiles",

    get toolbarItemLabel()
    {
        return WebInspector.UIString("Profiles");
    },

    get statusBarItems()
    {
        function clickHandler(profileType, buttonElement)
        {
            profileType.buttonClicked.call(profileType);
            this.updateProfileTypeButtons();
        }

        var items = [this.enableToggleButton.element];
        // FIXME: Generate a single "combo-button".
        for (var typeId in this._profileTypesByIdMap) {
            var profileType = this.getProfileType(typeId);
            if (profileType.buttonStyle) {
                var button = new WebInspector.StatusBarButton(profileType.buttonTooltip, profileType.buttonStyle, profileType.buttonCaption);
                this._profileTypeButtonsByIdMap[typeId] = button.element;
                button.element.addEventListener("click", clickHandler.bind(this, profileType, button.element), false);
                items.push(button.element);
            }
        }
        items.push(this.profileViewStatusBarItemsContainer);
        return items;
    },

    show: function()
    {
        WebInspector.Panel.prototype.show.call(this);
        if (this._shouldPopulateProfiles)
            this._populateProfiles();
    },

    populateInterface: function()
    {
        if (this.visible)
            this._populateProfiles();
        else
            this._shouldPopulateProfiles = true;
    },

    profilerWasEnabled: function()
    {
        this.reset();
        this.populateInterface();
    },

    profilerWasDisabled: function()
    {
        this.reset();
    },

    reset: function()
    {
        for (var i = 0; i < this._profiles.length; ++i)
            delete this._profiles[i]._profileView;

        delete this.currentQuery;
        this.searchCanceled();

        this._profiles = [];
        this._profilesIdMap = {};
        this._profileGroups = {};
        this._profileGroupsForLinks = {}

        this.sidebarTreeElement.removeStyleClass("some-expandable");

        for (var typeId in this._profileTypesByIdMap)
            this.getProfileType(typeId).treeElement.removeChildren();

        this.profileViews.removeChildren();

        this.profileViewStatusBarItemsContainer.removeChildren();

        this._updateInterface();
    },

    registerProfileType: function(profileType)
    {
        this._profileTypesByIdMap[profileType.id] = profileType;
        profileType.treeElement = new WebInspector.SidebarSectionTreeElement(profileType.name, null, true);
        this.sidebarTree.appendChild(profileType.treeElement);
        profileType.treeElement.expand();
    },

    _makeKey: function(text, profileTypeId)
    {
        return escape(text) + '/' + escape(profileTypeId);
    },

    addProfileHeader: function(profile)
    {
        var typeId = profile.typeId;
        var profileType = this.getProfileType(typeId);
        var sidebarParent = profileType.treeElement;
        var small = false;
        var alternateTitle;

        profile.__profilesPanelProfileType = profileType;
        this._profiles.push(profile);
        this._profilesIdMap[this._makeKey(profile.uid, typeId)] = profile;

        if (profile.title.indexOf(UserInitiatedProfileName) !== 0) {
            var profileTitleKey = this._makeKey(profile.title, typeId);
            if (!(profileTitleKey in this._profileGroups))
                this._profileGroups[profileTitleKey] = [];

            var group = this._profileGroups[profileTitleKey];
            group.push(profile);

            if (group.length === 2) {
                // Make a group TreeElement now that there are 2 profiles.
                group._profilesTreeElement = new WebInspector.ProfileGroupSidebarTreeElement(profile.title);

                // Insert at the same index for the first profile of the group.
                var index = sidebarParent.children.indexOf(group[0]._profilesTreeElement);
                sidebarParent.insertChild(group._profilesTreeElement, index);

                // Move the first profile to the group.
                var selected = group[0]._profilesTreeElement.selected;
                sidebarParent.removeChild(group[0]._profilesTreeElement);
                group._profilesTreeElement.appendChild(group[0]._profilesTreeElement);
                if (selected) {
                    group[0]._profilesTreeElement.select();
                    group[0]._profilesTreeElement.reveal();
                }

                group[0]._profilesTreeElement.small = true;
                group[0]._profilesTreeElement.mainTitle = WebInspector.UIString("Run %d", 1);

                this.sidebarTreeElement.addStyleClass("some-expandable");
            }

            if (group.length >= 2) {
                sidebarParent = group._profilesTreeElement;
                alternateTitle = WebInspector.UIString("Run %d", group.length);
                small = true;
            }
        }

        var profileTreeElement = profileType.createSidebarTreeElementForProfile(profile);
        profileTreeElement.small = small;
        if (alternateTitle)
            profileTreeElement.mainTitle = alternateTitle;
        profile._profilesTreeElement = profileTreeElement;

        sidebarParent.appendChild(profileTreeElement);
        if (!this.visibleView)
            this.showProfile(profile);
    },

    showProfile: function(profile)
    {
        if (!profile)
            return;

        if (this.visibleView)
            this.visibleView.hide();

        var view = profile.__profilesPanelProfileType.viewForProfile(profile);

        view.show(this.profileViews);

        profile._profilesTreeElement.select(true);
        profile._profilesTreeElement.reveal();

        this.visibleView = view;

        this.profileViewStatusBarItemsContainer.removeChildren();

        var statusBarItems = view.statusBarItems;
        for (var i = 0; i < statusBarItems.length; ++i)
            this.profileViewStatusBarItemsContainer.appendChild(statusBarItems[i]);
    },

    showView: function(view)
    {
        this.showProfile(view.profile);
    },

    getProfileType: function(typeId)
    {
        return this._profileTypesByIdMap[typeId];
    },

    showProfileForURL: function(url)
    {
        var match = url.match(WebInspector.ProfileType.URLRegExp);
        if (!match)
            return;
        this.showProfile(this._profilesIdMap[this._makeKey(match[3], match[1])]);
    },

    updateProfileTypeButtons: function()
    {
        for (var typeId in this._profileTypeButtonsByIdMap) {
            var buttonElement = this._profileTypeButtonsByIdMap[typeId];
            var profileType = this.getProfileType(typeId);
            buttonElement.className = profileType.buttonStyle;
            buttonElement.title = profileType.buttonTooltip;
            // FIXME: Apply profileType.buttonCaption once captions are added to button controls.
        }
    },

    closeVisibleView: function()
    {
        if (this.visibleView)
            this.visibleView.hide();
        delete this.visibleView;
    },

    displayTitleForProfileLink: function(title, typeId)
    {
        title = unescape(title);
        if (title.indexOf(UserInitiatedProfileName) === 0) {
            title = WebInspector.UIString("Profile %d", title.substring(UserInitiatedProfileName.length + 1));
        } else {
            var titleKey = this._makeKey(title, typeId);
            if (!(titleKey in this._profileGroupsForLinks))
                this._profileGroupsForLinks[titleKey] = 0;

            groupNumber = ++this._profileGroupsForLinks[titleKey];

            if (groupNumber > 2)
                // The title is used in the console message announcing that a profile has started so it gets
                // incremented twice as often as it's displayed
                title += " " + WebInspector.UIString("Run %d", groupNumber / 2);
        }
        
        return title;
    },

    get searchableViews()
    {
        var views = [];

        const visibleView = this.visibleView;
        if (visibleView && visibleView.performSearch)
            views.push(visibleView);

        var profilesLength = this._profiles.length;
        for (var i = 0; i < profilesLength; ++i) {
            var profile = this._profiles[i];
            var view = profile.__profilesPanelProfileType.viewForProfile(profile);
            if (!view.performSearch || view === visibleView)
                continue;
            views.push(view);
        }

        return views;
    },

    searchMatchFound: function(view, matches)
    {
        view.profile._profilesTreeElement.searchMatches = matches;
    },

    searchCanceled: function(startingNewSearch)
    {
        WebInspector.Panel.prototype.searchCanceled.call(this, startingNewSearch);

        if (!this._profiles)
            return;

        for (var i = 0; i < this._profiles.length; ++i) {
            var profile = this._profiles[i];
            profile._profilesTreeElement.searchMatches = 0;
        }
    },

    resize: function()
    {
        var visibleView = this.visibleView;
        if (visibleView && "resize" in visibleView)
            visibleView.resize();
    },

    _updateInterface: function()
    {
        // FIXME: Replace ProfileType-specific button visibility changes by a single ProfileType-agnostic "combo-button" visibility change.
        if (InspectorController.profilerEnabled()) {
            this.enableToggleButton.title = WebInspector.UIString("Profiling enabled. Click to disable.");
            this.enableToggleButton.toggled = true;
            for (var typeId in this._profileTypeButtonsByIdMap)
                this._profileTypeButtonsByIdMap[typeId].removeStyleClass("hidden");
            this.profileViewStatusBarItemsContainer.removeStyleClass("hidden");
            this.panelEnablerView.visible = false;
        } else {
            this.enableToggleButton.title = WebInspector.UIString("Profiling disabled. Click to enable.");
            this.enableToggleButton.toggled = false;
            for (var typeId in this._profileTypeButtonsByIdMap)
                this._profileTypeButtonsByIdMap[typeId].addStyleClass("hidden");
            this.profileViewStatusBarItemsContainer.addStyleClass("hidden");
            this.panelEnablerView.visible = true;
        }
    },

    _enableProfiling: function()
    {
        if (InspectorController.profilerEnabled())
            return;
        this._toggleProfiling(this.panelEnablerView.alwaysEnabled);
    },

    _toggleProfiling: function(optionalAlways)
    {
        if (InspectorController.profilerEnabled())
            InspectorController.disableProfiler(true);
        else
            InspectorController.enableProfiler(!!optionalAlways);
    },

    _populateProfiles: function()
    {
        var sidebarTreeChildrenCount = this.sidebarTree.children.length;
        for (var i = 0; i < sidebarTreeChildrenCount; ++i) {
            var treeElement = this.sidebarTree.children[i];
            if (treeElement.children.length)
                return;
        }

        function populateCallback(profileHeaders) {
            profileHeaders.sort(function(a, b) { return a.uid - b.uid; });
            var profileHeadersLength = profileHeaders.length;
            for (var i = 0; i < profileHeadersLength; ++i)
                WebInspector.addProfileHeader(profileHeaders[i]);
        }

        var callId = WebInspector.Callback.wrap(populateCallback);
        InspectorController.getProfileHeaders(callId);

        delete this._shouldPopulateProfiles;
    },

    updateMainViewWidth: function(width)
    {
        this.profileViews.style.left = width + "px";
        this.profileViewStatusBarItemsContainer.style.left = width + "px";
    }
}

WebInspector.ProfilesPanel.prototype.__proto__ = WebInspector.Panel.prototype;

WebInspector.ProfileSidebarTreeElement = function(profile)
{
    this.profile = profile;

    if (this.profile.title.indexOf(UserInitiatedProfileName) === 0)
        this._profileNumber = this.profile.title.substring(UserInitiatedProfileName.length + 1);

    WebInspector.SidebarTreeElement.call(this, "profile-sidebar-tree-item", "", "", profile, false);

    this.refreshTitles();
}

WebInspector.ProfileSidebarTreeElement.prototype = {
    onselect: function()
    {
        WebInspector.panels.profiles.showProfile(this.profile);
    },

    get mainTitle()
    {
        if (this._mainTitle)
            return this._mainTitle;
        if (this.profile.title.indexOf(UserInitiatedProfileName) === 0)
            return WebInspector.UIString("Profile %d", this._profileNumber);
        return this.profile.title;
    },

    set mainTitle(x)
    {
        this._mainTitle = x;
        this.refreshTitles();
    },

    get subtitle()
    {
        // There is no subtitle.
    },

    set subtitle(x)
    {
        // Can't change subtitle.
    },

    set searchMatches(matches)
    {
        if (!matches) {
            if (!this.bubbleElement)
                return;
            this.bubbleElement.removeStyleClass("search-matches");
            this.bubbleText = "";
            return;
        }

        this.bubbleText = matches;
        this.bubbleElement.addStyleClass("search-matches");
    }
}

WebInspector.ProfileSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype;

WebInspector.ProfileGroupSidebarTreeElement = function(title, subtitle)
{
    WebInspector.SidebarTreeElement.call(this, "profile-group-sidebar-tree-item", title, subtitle, null, true);
}

WebInspector.ProfileGroupSidebarTreeElement.prototype = {
    onselect: function()
    {
        WebInspector.panels.profiles.showProfile(this.children[this.children.length - 1].profile);
    }
}

WebInspector.ProfileGroupSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype;

WebInspector.didGetProfileHeaders = WebInspector.Callback.processCallback;
WebInspector.didGetProfile = WebInspector.Callback.processCallback;

/* ResourceView.js */

/*
 * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
 * Copyright (C) IBM Corp. 2009  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer. 
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution. 
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission. 
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.ResourceView = function(resource)
{
    WebInspector.View.call(this);

    this.element.addStyleClass("resource-view");

    this.resource = resource;

    this.headersElement = document.createElement("div");
    this.headersElement.className = "resource-view-headers";
    this.element.appendChild(this.headersElement);

    this.contentElement = document.createElement("div");
    this.contentElement.className = "resource-view-content";
    this.element.appendChild(this.contentElement);

    this.headersListElement = document.createElement("ol");
    this.headersListElement.className = "outline-disclosure";
    this.headersElement.appendChild(this.headersListElement);

    this.headersTreeOutline = new TreeOutline(this.headersListElement);
    this.headersTreeOutline.expandTreeElementsWhenArrowing = true;

    this.urlTreeElement = new TreeElement("", null, false);
    this.urlTreeElement.selectable = false;
    this.headersTreeOutline.appendChild(this.urlTreeElement);

    this.httpInformationTreeElement = new TreeElement("", null, true);
    this.httpInformationTreeElement.expanded = false;
    this.httpInformationTreeElement.selectable = false;
    this.headersTreeOutline.appendChild(this.httpInformationTreeElement);
     
    this.requestHeadersTreeElement = new TreeElement("", null, true);
    this.requestHeadersTreeElement.expanded = false;
    this.requestHeadersTreeElement.selectable = false;
    this.headersTreeOutline.appendChild(this.requestHeadersTreeElement);

    this._decodeHover = WebInspector.UIString("Double-Click to toggle between URL encoded and decoded formats");
    this._decodeRequestParameters = true;

    this.queryStringTreeElement = new TreeElement("", null, true);
    this.queryStringTreeElement.expanded = false;
    this.queryStringTreeElement.selectable = false;
    this.queryStringTreeElement.hidden = true;
    this.headersTreeOutline.appendChild(this.queryStringTreeElement);

    this.formDataTreeElement = new TreeElement("", null, true);
    this.formDataTreeElement.expanded = false;
    this.formDataTreeElement.selectable = false;
    this.formDataTreeElement.hidden = true;
    this.headersTreeOutline.appendChild(this.formDataTreeElement);

    this.requestPayloadTreeElement = new TreeElement(WebInspector.UIString("Request Payload"), null, true);
    this.requestPayloadTreeElement.expanded = false;
    this.requestPayloadTreeElement.selectable = false;
    this.requestPayloadTreeElement.hidden = true;
    this.headersTreeOutline.appendChild(this.requestPayloadTreeElement);

    this.responseHeadersTreeElement = new TreeElement("", null, true);
    this.responseHeadersTreeElement.expanded = false;
    this.responseHeadersTreeElement.selectable = false;
    this.headersTreeOutline.appendChild(this.responseHeadersTreeElement);

    this.headersVisible = true;

    resource.addEventListener("url changed", this._refreshURL, this);
    resource.addEventListener("requestHeaders changed", this._refreshRequestHeaders, this);
    resource.addEventListener("responseHeaders changed", this._refreshResponseHeaders, this);
    resource.addEventListener("finished", this._refreshHTTPInformation, this);

    this._refreshURL();
    this._refreshRequestHeaders();
    this._refreshResponseHeaders();
    this._refreshHTTPInformation();
}

WebInspector.ResourceView.prototype = {
    get headersVisible()
    {
        return this._headersVisible;
    },

    set headersVisible(x)
    {
        if (x === this._headersVisible)
            return;

        this._headersVisible = x;

        if (x)
            this.element.addStyleClass("headers-visible");
        else
            this.element.removeStyleClass("headers-visible");
    },

    attach: function()
    {
        if (!this.element.parentNode) {
            var parentElement = (document.getElementById("resource-views") || document.getElementById("script-resource-views"));
            if (parentElement)
                parentElement.appendChild(this.element);
        }
    },

    _refreshURL: function()
    {
        var url = this.resource.url;
        var statusCodeImage = "";
        if (this.resource.statusCode) {
            var statusImageSource = "";
            
            if (this.resource.statusCode < 300)
                statusImageSource = "Images/successGreenDot.png";
            else if (this.resource.statusCode < 400)
                statusImageSource = "Images/warningOrangeDot.png";
            else
                statusImageSource = "Images/errorRedDot.png";
        
            statusCodeImage = "<img class=\"resource-status-image\" src=\"" + statusImageSource + "\" title=\"" + WebInspector.Resource.StatusTextForCode(this.resource.statusCode) + "\">";
        }

        this.urlTreeElement.title = statusCodeImage + "<span class=\"resource-url\">" + url.escapeHTML() + "</span>";
        this._refreshQueryString();
    },

    _refreshQueryString: function()
    {
        var url = this.resource.url;
        var hasQueryString = url.indexOf("?") >= 0;

        if (!hasQueryString) {
            this.queryStringTreeElement.hidden = true;
            return;
        }

        this.queryStringTreeElement.hidden = false;
        var parmString = url.split("?", 2)[1];
        this._refreshParms(WebInspector.UIString("Query String Parameters"), parmString, this.queryStringTreeElement);
    },

    _refreshFormData: function()
    {
        this.formDataTreeElement.hidden = true;
        this.requestPayloadTreeElement.hidden = true;

        var isFormData = this.resource.requestFormData;
        if (!isFormData)
            return;

        var isFormEncoded = false;
        var requestContentType = this._getHeaderValue(this.resource.requestHeaders, "Content-Type");
        if (requestContentType && requestContentType.match(/^application\/x-www-form-urlencoded\s*(;.*)?$/i))
            isFormEncoded = true;

        if (isFormEncoded) {
            this.formDataTreeElement.hidden = false;
            this._refreshParms(WebInspector.UIString("Form Data"), this.resource.requestFormData, this.formDataTreeElement);
        } else {
            this.requestPayloadTreeElement.hidden = false;
            this._refreshRequestPayload(this.resource.requestFormData);
        }
    },

    _refreshRequestPayload: function(formData)
    {
        this.requestPayloadTreeElement.removeChildren();

        var title = "<div class=\"header-name\">&nbsp;</div>";
        title += "<div class=\"raw-form-data header-value\">" + formData.escapeHTML() + "</div>";
        var parmTreeElement = new TreeElement(title, null, false);
        parmTreeElement.selectable = false;
        this.requestPayloadTreeElement.appendChild(parmTreeElement);
    },

    _refreshParms: function(title, parmString, parmsTreeElement)
    {
        var parms = parmString.split("&");
        for (var i = 0; i < parms.length; ++i) {
            var parm = parms[i];
            parm = parm.split("=", 2);
            if (parm.length == 1)
                parm.push("");
            parms[i] = parm;
        }

        parmsTreeElement.removeChildren();

        parmsTreeElement.title = title + "<span class=\"header-count\">" + WebInspector.UIString(" (%d)", parms.length) + "</span>";

        for (var i = 0; i < parms.length; ++i) {
            var key = parms[i][0];
            var value = parms[i][1];

            var errorDecoding = false;
            if (this._decodeRequestParameters) {
                if (value.indexOf("%") >= 0) {
                    try {
                        value = decodeURIComponent(value);
                    } catch(e) {
                        errorDecoding = true;
                    }
                }
                    
                value = value.replace(/\+/g, " ");
            }

            valueEscaped = value.escapeHTML();
            if (errorDecoding)
                valueEscaped += " <span class=\"error-message\">" + WebInspector.UIString("(unable to decode value)").escapeHTML() + "</span>";

            var title = "<div class=\"header-name\">" + key.escapeHTML() + ":</div>";
            title += "<div class=\"header-value\">" + valueEscaped + "</div>";

            var parmTreeElement = new TreeElement(title, null, false);
            parmTreeElement.selectable = false;
            parmTreeElement.tooltip = this._decodeHover;
            parmTreeElement.ondblclick = this._toggleURLdecoding.bind(this);
            parmsTreeElement.appendChild(parmTreeElement);
        }
    },

    _toggleURLdecoding: function(treeElement, event)
    {
        this._decodeRequestParameters = !this._decodeRequestParameters;
        this._refreshQueryString();
        this._refreshFormData();
    },

    _getHeaderValue: function(headers, key)
    {
        var lowerKey = key.toLowerCase();
        for (var testKey in headers) {
            if (testKey.toLowerCase() === lowerKey)
                return headers[testKey];
        }
    },

    _refreshRequestHeaders: function()
    {
        this._refreshHeaders(WebInspector.UIString("Request Headers"), this.resource.sortedRequestHeaders, this.requestHeadersTreeElement);
        this._refreshFormData();
    },

    _refreshResponseHeaders: function()
    {
        this._refreshHeaders(WebInspector.UIString("Response Headers"), this.resource.sortedResponseHeaders, this.responseHeadersTreeElement);
    },

    _refreshHTTPInformation: function()
    {
        const listElements = 2;

        var headerElement = this.httpInformationTreeElement;
        headerElement.removeChildren();
        headerElement.hidden = !this.resource.statusCode;

        if (this.resource.statusCode) {
            headerElement.title = WebInspector.UIString("HTTP Information") +  "<span class=\"header-count\">" + WebInspector.UIString(" (%d)", listElements) + "</span>";
        
            var title = "<div class=\"header-name\">" + WebInspector.UIString("Request Method") + ":</div>";
            title += "<div class=\"header-value\">" + this.resource.requestMethod + "</div>"
            
            var headerTreeElement = new TreeElement(title, null, false);
            headerTreeElement.selectable = false;
            headerElement.appendChild(headerTreeElement);
            
            title = "<div class=\"header-name\">" + WebInspector.UIString("Status Code") + ":</div>";
            title += "<div class=\"header-value\">" + WebInspector.Resource.StatusTextForCode(this.resource.statusCode) + "</div>"
            
            headerTreeElement = new TreeElement(title, null, false);
            headerTreeElement.selectable = false;
            headerElement.appendChild(headerTreeElement);
        }
    },
    
    _refreshHeaders: function(title, headers, headersTreeElement)
    {
        headersTreeElement.removeChildren();

        var length = headers.length;
        headersTreeElement.title = title.escapeHTML() + "<span class=\"header-count\">" + WebInspector.UIString(" (%d)", length) + "</span>";
        headersTreeElement.hidden = !length;

        var length = headers.length;
        for (var i = 0; i < length; ++i) {
            var title = "<div class=\"header-name\">" + headers[i].header.escapeHTML() + ":</div>";
            title += "<div class=\"header-value\">" + headers[i].value.escapeHTML() + "</div>"

            var headerTreeElement = new TreeElement(title, null, false);
            headerTreeElement.selectable = false;
            headersTreeElement.appendChild(headerTreeElement);
        }
    }
}

WebInspector.ResourceView.prototype.__proto__ = WebInspector.View.prototype;

/* SourceFrame.js */

/*
 * Copyright (C) 2008 Apple Inc. All Rights Reserved.
 * Copyright (C) 2009 Joseph Pecoraro
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.SourceFrame = function(element, addBreakpointDelegate)
{
    this.messages = [];
    this.breakpoints = [];
    this._shortcuts = {};

    this.addBreakpointDelegate = addBreakpointDelegate;

    this.element = element || document.createElement("iframe");
    this.element.addStyleClass("source-view-frame");
    this.element.setAttribute("viewsource", "true");

    this.element.addEventListener("load", this._loaded.bind(this), false);
}

WebInspector.SourceFrame.prototype = {
    get executionLine()
    {
        return this._executionLine;
    },

    set executionLine(x)
    {
        if (this._executionLine === x)
            return;

        var previousLine = this._executionLine;
        this._executionLine = x;

        this._updateExecutionLine(previousLine);
    },

    get autoSizesToFitContentHeight()
    {
        return this._autoSizesToFitContentHeight;
    },

    set autoSizesToFitContentHeight(x)
    {
        if (this._autoSizesToFitContentHeight === x)
            return;

        this._autoSizesToFitContentHeight = x;

        if (this._autoSizesToFitContentHeight) {
            this._windowResizeListener = this._windowResized.bind(this);
            window.addEventListener("resize", this._windowResizeListener, false);
            this.sizeToFitContentHeight();
        } else {
            this.element.style.removeProperty("height");
            if (this.element.contentDocument)
                this.element.contentDocument.body.removeStyleClass("webkit-height-sized-to-fit");
            window.removeEventListener("resize", this._windowResizeListener, false);
            delete this._windowResizeListener;
        }
    },

    sourceRow: function(lineNumber)
    {
        if (!lineNumber || !this.element.contentDocument)
            return;

        var table = this.element.contentDocument.getElementsByTagName("table")[0];
        if (!table)
            return;

        var rows = table.rows;

        // Line numbers are a 1-based index, but the rows collection is 0-based.
        --lineNumber;

        return rows[lineNumber];
    },

    lineNumberForSourceRow: function(sourceRow)
    {
        // Line numbers are a 1-based index, but the rows collection is 0-based.
        var lineNumber = 0;
        while (sourceRow) {
            ++lineNumber;
            sourceRow = sourceRow.previousSibling;
        }

        return lineNumber;
    },

    revealLine: function(lineNumber)
    {
        if (!this._isContentLoaded()) {
            this._lineNumberToReveal = lineNumber;
            return;
        }

        var row = this.sourceRow(lineNumber);
        if (row)
            row.scrollIntoViewIfNeeded(true);
    },

    addBreakpoint: function(breakpoint)
    {
        this.breakpoints.push(breakpoint);
        breakpoint.addEventListener("enabled", this._breakpointEnableChanged, this);
        breakpoint.addEventListener("disabled", this._breakpointEnableChanged, this);
        this._addBreakpointToSource(breakpoint);
    },

    removeBreakpoint: function(breakpoint)
    {
        this.breakpoints.remove(breakpoint);
        breakpoint.removeEventListener("enabled", null, this);
        breakpoint.removeEventListener("disabled", null, this);
        this._removeBreakpointFromSource(breakpoint);
    },

    addMessage: function(msg)
    {
        // Don't add the message if there is no message or valid line or if the msg isn't an error or warning.
        if (!msg.message || msg.line <= 0 || !msg.isErrorOrWarning())
            return;
        this.messages.push(msg);
        this._addMessageToSource(msg);
    },

    clearMessages: function()
    {
        this.messages = [];

        if (!this.element.contentDocument)
            return;

        var bubbles = this.element.contentDocument.querySelectorAll(".webkit-html-message-bubble");
        if (!bubbles)
            return;

        for (var i = 0; i < bubbles.length; ++i) {
            var bubble = bubbles[i];
            bubble.parentNode.removeChild(bubble);
        }
    },

    sizeToFitContentHeight: function()
    {
        if (this.element.contentDocument) {
            this.element.style.setProperty("height", this.element.contentDocument.body.offsetHeight + "px");
            this.element.contentDocument.body.addStyleClass("webkit-height-sized-to-fit");
        }
    },

    _highlightLineEnds: function(event)
    {
        event.target.parentNode.removeStyleClass("webkit-highlighted-line");
    },

    highlightLine: function(lineNumber)
    {
        if (!this._isContentLoaded()) {
            this._lineNumberToHighlight = lineNumber;
            return;
        }

        var sourceRow = this.sourceRow(lineNumber);
        if (!sourceRow)
            return;
        var line = sourceRow.getElementsByClassName('webkit-line-content')[0];
        // Trick to reset the animation if the user clicks on the same link
        // Using a timeout to avoid coalesced style updates
        line.style.setProperty("-webkit-animation-name", "none");
        setTimeout(function () {
            line.style.removeProperty("-webkit-animation-name");
            sourceRow.addStyleClass("webkit-highlighted-line");
        }, 0);
    },

    _loaded: function()
    {
        WebInspector.addMainEventListeners(this.element.contentDocument);
        this.element.contentDocument.addEventListener("contextmenu", this._documentContextMenu.bind(this), true);
        this.element.contentDocument.addEventListener("mousedown", this._documentMouseDown.bind(this), true);
        this.element.contentDocument.addEventListener("keydown", this._documentKeyDown.bind(this), true);
        this.element.contentDocument.addEventListener("keyup", WebInspector.documentKeyUp.bind(WebInspector), true);
        this.element.contentDocument.addEventListener("webkitAnimationEnd", this._highlightLineEnds.bind(this), false);

        // Register 'eval' shortcut.
        var isMac = InspectorController.platform().indexOf("mac-") === 0;
        var platformSpecificModifier = isMac ? WebInspector.KeyboardShortcut.Modifiers.Meta : WebInspector.KeyboardShortcut.Modifiers.Ctrl;
        var shortcut = WebInspector.KeyboardShortcut.makeKey(69 /* 'E' */, platformSpecificModifier | WebInspector.KeyboardShortcut.Modifiers.Shift);
        this._shortcuts[shortcut] = this._evalSelectionInCallFrame.bind(this);

        var headElement = this.element.contentDocument.getElementsByTagName("head")[0];
        if (!headElement) {
            headElement = this.element.contentDocument.createElement("head");
            this.element.contentDocument.documentElement.insertBefore(headElement, this.element.contentDocument.documentElement.firstChild);
        }
        
        var linkElement = this.element.contentDocument.createElement("link");
        linkElement.type = "text/css";
        linkElement.rel = "stylesheet";
        linkElement.href = "inspectorSyntaxHighlight.css";
        headElement.appendChild(linkElement);

        var styleElement = this.element.contentDocument.createElement("style");
        headElement.appendChild(styleElement);

        // Add these style rules here since they are specific to the Inspector. They also behave oddly and not
        // all properties apply if added to view-source.css (because it is a user agent sheet.)
        var styleText = ".webkit-line-number { background-repeat: no-repeat; background-position: right 1px; }\n";
        styleText += ".webkit-execution-line .webkit-line-number { color: transparent; background-image: -webkit-canvas(program-counter); }\n";

        styleText += ".webkit-breakpoint .webkit-line-number { color: white; background-image: -webkit-canvas(breakpoint); }\n";
        styleText += ".webkit-breakpoint-disabled .webkit-line-number { color: white; background-image: -webkit-canvas(breakpoint-disabled); }\n";
        styleText += ".webkit-breakpoint.webkit-execution-line .webkit-line-number { color: transparent; background-image: -webkit-canvas(breakpoint-program-counter); }\n";
        styleText += ".webkit-breakpoint-disabled.webkit-execution-line .webkit-line-number { color: transparent; background-image: -webkit-canvas(breakpoint-disabled-program-counter); }\n";

        styleText += ".webkit-breakpoint.webkit-breakpoint-conditional .webkit-line-number { color: white; background-image: -webkit-canvas(breakpoint-conditional); }\n";
        styleText += ".webkit-breakpoint-disabled.webkit-breakpoint-conditional .webkit-line-number { color: white; background-image: -webkit-canvas(breakpoint-disabled-conditional); }\n";
        styleText += ".webkit-breakpoint.webkit-breakpoint-conditional.webkit-execution-line .webkit-line-number { color: transparent; background-image: -webkit-canvas(breakpoint-conditional-program-counter); }\n";
        styleText += ".webkit-breakpoint-disabled.webkit-breakpoint-conditional.webkit-execution-line .webkit-line-number { color: transparent; background-image: -webkit-canvas(breakpoint-disabled-conditional-program-counter); }\n";

        styleText += ".webkit-execution-line .webkit-line-content { background-color: rgb(171, 191, 254); outline: 1px solid rgb(64, 115, 244); }\n";
        styleText += ".webkit-height-sized-to-fit { overflow-y: hidden }\n";
        styleText += ".webkit-line-content { background-color: white; }\n";
        styleText += "@-webkit-keyframes fadeout {from {background-color: rgb(255, 255, 120);} to { background-color: white;}}\n";
        styleText += ".webkit-highlighted-line .webkit-line-content { background-color: rgb(255, 255, 120); -webkit-animation: 'fadeout' 2s 500ms}\n";

        // TODO: Move these styles into inspector.css once https://bugs.webkit.org/show_bug.cgi?id=28913 is fixed and popup moved into the top frame.
        styleText += ".popup-content { position: absolute; z-index: 10000; padding: 4px; background-color: rgb(203, 226, 255); -webkit-border-radius: 7px; border: 2px solid rgb(169, 172, 203); }";
        styleText += ".popup-glasspane { position: absolute; top: 0; left: 0; height: 100%; width: 100%; opacity: 0; z-index: 9900; }";
        styleText += ".popup-message { background-color: transparent; font-family: Lucida Grande, sans-serif; font-weight: normal; font-size: 11px; text-align: left; text-shadow: none; color: rgb(85, 85, 85); cursor: default; margin: 0 0 2px 0; }";
        styleText += ".popup-content.breakpoint-condition { width: 90%; }";
        styleText += ".popup-content input#bp-condition { font-family: monospace; margin: 0; border: 1px inset rgb(190, 190, 190) !important; width: 100%; box-shadow: none !important; outline: none !important; -webkit-user-modify: read-write; }";
        // This class is already in inspector.css
        styleText += ".hidden { display: none !important; }";

        styleElement.textContent = styleText;

        this._needsProgramCounterImage = true;
        this._needsBreakpointImages = true;

        this.element.contentWindow.Element.prototype.addStyleClass = Element.prototype.addStyleClass;
        this.element.contentWindow.Element.prototype.removeStyleClass = Element.prototype.removeStyleClass;
        this.element.contentWindow.Element.prototype.removeChildren = Element.prototype.removeChildren;
        this.element.contentWindow.Element.prototype.positionAt = Element.prototype.positionAt;
        this.element.contentWindow.Element.prototype.removeMatchingStyleClasses = Element.prototype.removeMatchingStyleClasses;
        this.element.contentWindow.Element.prototype.hasStyleClass = Element.prototype.hasStyleClass;
        this.element.contentWindow.Element.prototype.pageOffsetRelativeToWindow = Element.prototype.pageOffsetRelativeToWindow;
        this.element.contentWindow.Element.prototype.__defineGetter__("totalOffsetLeft", Element.prototype.__lookupGetter__("totalOffsetLeft"));
        this.element.contentWindow.Element.prototype.__defineGetter__("totalOffsetTop", Element.prototype.__lookupGetter__("totalOffsetTop"));
        this.element.contentWindow.Node.prototype.enclosingNodeOrSelfWithNodeName = Node.prototype.enclosingNodeOrSelfWithNodeName;
        this.element.contentWindow.Node.prototype.enclosingNodeOrSelfWithNodeNameInArray = Node.prototype.enclosingNodeOrSelfWithNodeNameInArray;

        this._addExistingMessagesToSource();
        this._addExistingBreakpointsToSource();
        this._updateExecutionLine();
        if (this._executionLine)
            this.revealLine(this._executionLine);

        if (this.autoSizesToFitContentHeight)
            this.sizeToFitContentHeight();

        if (this._lineNumberToReveal) {
            this.revealLine(this._lineNumberToReveal);
            delete this._lineNumberToReveal;
        }

        if (this._lineNumberToHighlight) {
            this.highlightLine(this._lineNumberToHighlight);
            delete this._lineNumberToHighlight;
        }

        this.dispatchEventToListeners("content loaded");
    },

    _isContentLoaded: function() {
        var doc = this.element.contentDocument;
        return doc && doc.getElementsByTagName("table")[0];
    },

    _windowResized: function(event)
    {
        if (!this._autoSizesToFitContentHeight)
            return;
        this.sizeToFitContentHeight();
    },

    _documentContextMenu: function(event)
    {
        if (!event.target.hasStyleClass("webkit-line-number"))
            return;
        var sourceRow = event.target.enclosingNodeOrSelfWithNodeName("tr");
        if (!sourceRow._breakpointObject && this.addBreakpointDelegate)
            this.addBreakpointDelegate(this.lineNumberForSourceRow(sourceRow));

        var breakpoint = sourceRow._breakpointObject;
        if (!breakpoint)
            return;

        this._editBreakpointCondition(event.target, sourceRow, breakpoint);
        event.preventDefault();
    },

    _documentMouseDown: function(event)
    {
        if (!event.target.hasStyleClass("webkit-line-number"))
            return;
        if (event.button != 0 || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey)
            return;
        var sourceRow = event.target.enclosingNodeOrSelfWithNodeName("tr");
        if (sourceRow._breakpointObject && sourceRow._breakpointObject.enabled)
            sourceRow._breakpointObject.enabled = false;
        else if (sourceRow._breakpointObject)
            WebInspector.panels.scripts.removeBreakpoint(sourceRow._breakpointObject);
        else if (this.addBreakpointDelegate)
            this.addBreakpointDelegate(this.lineNumberForSourceRow(sourceRow));

        event.preventDefault();
    },

    _editBreakpointCondition: function(eventTarget, sourceRow, breakpoint)
    {
        // TODO: Migrate the popup to the top-level document and remove the blur listener from conditionElement once https://bugs.webkit.org/show_bug.cgi?id=28913 is fixed.
        var popupDocument = this.element.contentDocument;
        this._showBreakpointConditionPopup(eventTarget, breakpoint.line, popupDocument);

        function committed(element, newText)
        {
            breakpoint.condition = newText;
            if (breakpoint.condition)
                sourceRow.addStyleClass("webkit-breakpoint-conditional");
            else
                sourceRow.removeStyleClass("webkit-breakpoint-conditional");
            dismissed.call(this);
        }

        function dismissed()
        {
            this._popup.hide();
            delete this._conditionEditorElement;
        }

        var dismissedHandler = dismissed.bind(this);
        this._conditionEditorElement.addEventListener("blur", dismissedHandler, false);

        WebInspector.startEditing(this._conditionEditorElement, committed.bind(this), dismissedHandler);
        this._conditionEditorElement.value = breakpoint.condition;
        this._conditionEditorElement.select();
    },

    _showBreakpointConditionPopup: function(clickedElement, lineNumber, popupDocument)
    {
        var popupContentElement = this._createPopupElement(lineNumber, popupDocument);
        var lineElement = clickedElement.enclosingNodeOrSelfWithNodeName("td").nextSibling;
        if (this._popup) {
            this._popup.hide();
            this._popup.element = popupContentElement;
        } else {
            this._popup = new WebInspector.Popup(popupContentElement);
            this._popup.autoHide = true;
        }
        this._popup.anchor = lineElement;
        this._popup.show();
    },

    _createPopupElement: function(lineNumber, popupDocument)
    {
        var popupContentElement = popupDocument.createElement("div");
        popupContentElement.className = "popup-content breakpoint-condition";

        var labelElement = document.createElement("label");
        labelElement.className = "popup-message";
        labelElement.htmlFor = "bp-condition";
        labelElement.appendChild(document.createTextNode(WebInspector.UIString("The breakpoint on line %d will stop only if this expression is true:", lineNumber)));
        popupContentElement.appendChild(labelElement);

        var editorElement = document.createElement("input");
        editorElement.id = "bp-condition";
        editorElement.type = "text"
        popupContentElement.appendChild(editorElement);
        this._conditionEditorElement = editorElement;

        return popupContentElement;
    },

    _documentKeyDown: function(event)
    {
        var shortcut = WebInspector.KeyboardShortcut.makeKeyFromEvent(event);
        var handler = this._shortcuts[shortcut];
        if (handler) {
            handler(event);
            event.preventDefault();
        } else {
            WebInspector.documentKeyDown(event);
        }
    },

    _evalSelectionInCallFrame: function(event)
    {
        if (!WebInspector.panels.scripts || !WebInspector.panels.scripts.paused)
            return;

        var selection = this.element.contentWindow.getSelection();
        if (!selection.rangeCount)
            return;

        var expression = selection.getRangeAt(0).toString().trimWhitespace();
        WebInspector.panels.scripts.evaluateInSelectedCallFrame(expression, false, "console", function(result, exception) {
            WebInspector.showConsole();
            var commandMessage = new WebInspector.ConsoleCommand(expression);
            WebInspector.console.addMessage(commandMessage);
            WebInspector.console.addMessage(new WebInspector.ConsoleCommandResult(result, exception, commandMessage));
        });
    },

    _breakpointEnableChanged: function(event)
    {
        var breakpoint = event.target;
        var sourceRow = this.sourceRow(breakpoint.line);
        if (!sourceRow)
            return;

        sourceRow.addStyleClass("webkit-breakpoint");

        if (breakpoint.enabled)
            sourceRow.removeStyleClass("webkit-breakpoint-disabled");
        else
            sourceRow.addStyleClass("webkit-breakpoint-disabled");
    },

    _updateExecutionLine: function(previousLine)
    {
        if (previousLine) {
            var sourceRow = this.sourceRow(previousLine);
            if (sourceRow)
                sourceRow.removeStyleClass("webkit-execution-line");
        }

        if (!this._executionLine)
            return;

        this._drawProgramCounterImageIfNeeded();

        var sourceRow = this.sourceRow(this._executionLine);
        if (sourceRow)
            sourceRow.addStyleClass("webkit-execution-line");
    },

    _addExistingBreakpointsToSource: function()
    {
        var length = this.breakpoints.length;
        for (var i = 0; i < length; ++i)
            this._addBreakpointToSource(this.breakpoints[i]);
    },

    _addBreakpointToSource: function(breakpoint)
    {
        var sourceRow = this.sourceRow(breakpoint.line);
        if (!sourceRow)
            return;

        breakpoint.sourceText = sourceRow.getElementsByClassName('webkit-line-content')[0].textContent;

        this._drawBreakpointImagesIfNeeded();

        sourceRow._breakpointObject = breakpoint;

        sourceRow.addStyleClass("webkit-breakpoint");
        if (!breakpoint.enabled)
            sourceRow.addStyleClass("webkit-breakpoint-disabled");
        if (breakpoint.condition)
            sourceRow.addStyleClass("webkit-breakpoint-conditional");
    },

    _removeBreakpointFromSource: function(breakpoint)
    {
        var sourceRow = this.sourceRow(breakpoint.line);
        if (!sourceRow)
            return;

        delete sourceRow._breakpointObject;

        sourceRow.removeStyleClass("webkit-breakpoint");
        sourceRow.removeStyleClass("webkit-breakpoint-disabled");
        sourceRow.removeStyleClass("webkit-breakpoint-conditional");
    },

    _incrementMessageRepeatCount: function(msg, repeatDelta)
    {
        if (!msg._resourceMessageLineElement)
            return;

        if (!msg._resourceMessageRepeatCountElement) {
            var repeatedElement = document.createElement("span");
            msg._resourceMessageLineElement.appendChild(repeatedElement);
            msg._resourceMessageRepeatCountElement = repeatedElement;
        }

        msg.repeatCount += repeatDelta;
        msg._resourceMessageRepeatCountElement.textContent = WebInspector.UIString(" (repeated %d times)", msg.repeatCount);
    },

    _addExistingMessagesToSource: function()
    {
        var length = this.messages.length;
        for (var i = 0; i < length; ++i)
            this._addMessageToSource(this.messages[i]);
    },

    _addMessageToSource: function(msg)
    {
        var row = this.sourceRow(msg.line);
        if (!row)
            return;

        var cell = row.cells[1];
        if (!cell)
            return;

        var messageBubbleElement = cell.lastChild;
        if (!messageBubbleElement || messageBubbleElement.nodeType !== Node.ELEMENT_NODE || !messageBubbleElement.hasStyleClass("webkit-html-message-bubble")) {
            messageBubbleElement = this.element.contentDocument.createElement("div");
            messageBubbleElement.className = "webkit-html-message-bubble";
            cell.appendChild(messageBubbleElement);
        }

        if (!row.messages)
            row.messages = [];

        for (var i = 0; i < row.messages.length; ++i) {
            if (row.messages[i].isEqual(msg, true)) {
                this._incrementMessageRepeatCount(row.messages[i], msg.repeatDelta);
                return;
            }
        }

        row.messages.push(msg);

        var imageURL;
        switch (msg.level) {
            case WebInspector.ConsoleMessage.MessageLevel.Error:
                messageBubbleElement.addStyleClass("webkit-html-error-message");
                imageURL = "Images/errorIcon.png";
                break;
            case WebInspector.ConsoleMessage.MessageLevel.Warning:
                messageBubbleElement.addStyleClass("webkit-html-warning-message");
                imageURL = "Images/warningIcon.png";
                break;
        }

        var messageLineElement = this.element.contentDocument.createElement("div");
        messageLineElement.className = "webkit-html-message-line";
        messageBubbleElement.appendChild(messageLineElement);

        // Create the image element in the Inspector's document so we can use relative image URLs.
        var image = document.createElement("img");
        image.src = imageURL;
        image.className = "webkit-html-message-icon";

        // Adopt the image element since it wasn't created in element's contentDocument.
        image = this.element.contentDocument.adoptNode(image);
        messageLineElement.appendChild(image);
        messageLineElement.appendChild(this.element.contentDocument.createTextNode(msg.message));

        msg._resourceMessageLineElement = messageLineElement;
    },

    _drawProgramCounterInContext: function(ctx, glow)
    {
        if (glow)
            ctx.save();

        ctx.beginPath();
        ctx.moveTo(17, 2);
        ctx.lineTo(19, 2);
        ctx.lineTo(19, 0);
        ctx.lineTo(21, 0);
        ctx.lineTo(26, 5.5);
        ctx.lineTo(21, 11);
        ctx.lineTo(19, 11);
        ctx.lineTo(19, 9);
        ctx.lineTo(17, 9);
        ctx.closePath();
        ctx.fillStyle = "rgb(142, 5, 4)";

        if (glow) {
            ctx.shadowBlur = 4;
            ctx.shadowColor = "rgb(255, 255, 255)";
            ctx.shadowOffsetX = -1;
            ctx.shadowOffsetY = 0;
        }

        ctx.fill();
        ctx.fill(); // Fill twice to get a good shadow and darker anti-aliased pixels.

        if (glow)
            ctx.restore();
    },

    _drawProgramCounterImageIfNeeded: function()
    {
        if (!this._needsProgramCounterImage || !this.element.contentDocument)
            return;

        var ctx = this.element.contentDocument.getCSSCanvasContext("2d", "program-counter", 26, 11);
        ctx.clearRect(0, 0, 26, 11);
        this._drawProgramCounterInContext(ctx, true);

        delete this._needsProgramCounterImage;
    },

    _drawBreakpointImagesIfNeeded: function(conditional)
    {
        if (!this._needsBreakpointImages || !this.element.contentDocument)
            return;

        function drawBreakpoint(ctx, disabled, conditional)
        {
            ctx.beginPath();
            ctx.moveTo(0, 2);
            ctx.lineTo(2, 0);
            ctx.lineTo(21, 0);
            ctx.lineTo(26, 5.5);
            ctx.lineTo(21, 11);
            ctx.lineTo(2, 11);
            ctx.lineTo(0, 9);
            ctx.closePath();
            ctx.fillStyle = conditional ? "rgb(217, 142, 1)" : "rgb(1, 142, 217)";
            ctx.strokeStyle = conditional ? "rgb(205, 103, 0)" : "rgb(0, 103, 205)";
            ctx.lineWidth = 3;
            ctx.fill();
            ctx.save();
            ctx.clip();
            ctx.stroke();
            ctx.restore();

            if (!disabled)
                return;

            ctx.save();
            ctx.globalCompositeOperation = "destination-out";
            ctx.fillStyle = "rgba(0, 0, 0, 0.5)";
            ctx.fillRect(0, 0, 26, 11);
            ctx.restore();
        }


        // Unconditional breakpoints.

        var ctx = this.element.contentDocument.getCSSCanvasContext("2d", "breakpoint", 26, 11);
        ctx.clearRect(0, 0, 26, 11);
        drawBreakpoint(ctx);

        var ctx = this.element.contentDocument.getCSSCanvasContext("2d", "breakpoint-program-counter", 26, 11);
        ctx.clearRect(0, 0, 26, 11);
        drawBreakpoint(ctx);
        ctx.clearRect(20, 0, 6, 11);
        this._drawProgramCounterInContext(ctx, true);

        var ctx = this.element.contentDocument.getCSSCanvasContext("2d", "breakpoint-disabled", 26, 11);
        ctx.clearRect(0, 0, 26, 11);
        drawBreakpoint(ctx, true);

        var ctx = this.element.contentDocument.getCSSCanvasContext("2d", "breakpoint-disabled-program-counter", 26, 11);
        ctx.clearRect(0, 0, 26, 11);
        drawBreakpoint(ctx, true);
        ctx.clearRect(20, 0, 6, 11);
        this._drawProgramCounterInContext(ctx, true);


        // Conditional breakpoints.

        var ctx = this.element.contentDocument.getCSSCanvasContext("2d", "breakpoint-conditional", 26, 11);
        ctx.clearRect(0, 0, 26, 11);
        drawBreakpoint(ctx, false, true);

        var ctx = this.element.contentDocument.getCSSCanvasContext("2d", "breakpoint-conditional-program-counter", 26, 11);
        ctx.clearRect(0, 0, 26, 11);
        drawBreakpoint(ctx, false, true);
        ctx.clearRect(20, 0, 6, 11);
        this._drawProgramCounterInContext(ctx, true);

        var ctx = this.element.contentDocument.getCSSCanvasContext("2d", "breakpoint-disabled-conditional", 26, 11);
        ctx.clearRect(0, 0, 26, 11);
        drawBreakpoint(ctx, true, true);

        var ctx = this.element.contentDocument.getCSSCanvasContext("2d", "breakpoint-disabled-conditional-program-counter", 26, 11);
        ctx.clearRect(0, 0, 26, 11);
        drawBreakpoint(ctx, true, true);
        ctx.clearRect(20, 0, 6, 11);
        this._drawProgramCounterInContext(ctx, true);

        delete this._needsBreakpointImages;
    },

    syntaxHighlightJavascript: function()
    {
        var table = this.element.contentDocument.getElementsByTagName("table")[0];
        if (!table)
            return;

        var jsSyntaxHighlighter = new WebInspector.JavaScriptSourceSyntaxHighlighter(table, this);
        jsSyntaxHighlighter.process();
    },

    syntaxHighlightCSS: function()
    {
        var table = this.element.contentDocument.getElementsByTagName("table")[0];
        if (!table)
            return;

        var cssSyntaxHighlighter = new WebInspector.CSSSourceSyntaxHighlighter(table, this);
        cssSyntaxHighlighter.process();
    }
}

WebInspector.SourceFrame.prototype.__proto__ = WebInspector.Object.prototype;

WebInspector.SourceSyntaxHighlighter = function(table, sourceFrame)
{
    this.table = table;
    this.sourceFrame = sourceFrame;
}

WebInspector.SourceSyntaxHighlighter.prototype = {
    createSpan: function(content, className)
    {
        var span = document.createElement("span");
        span.className = className;
        span.appendChild(document.createTextNode(content));
        return span;
    },

    process: function()
    {
        // Split up the work into chunks so we don't block the
        // UI thread while processing.

        var rows = this.table.rows;
        var rowsLength = rows.length;
        const tokensPerChunk = 100;
        const lineLengthLimit = 20000;
        
        var boundProcessChunk = processChunk.bind(this);
        var processChunkInterval = setInterval(boundProcessChunk, 25);
        boundProcessChunk();
        
        function processChunk()
        {
            for (var i = 0; i < tokensPerChunk; i++) {
                if (this.cursor >= this.lineCode.length)
                    moveToNextLine.call(this);
                if (this.lineIndex >= rowsLength) {
                    this.sourceFrame.dispatchEventToListeners("syntax highlighting complete");
                    return;
                }
                if (this.cursor > lineLengthLimit) {
                    var codeFragment = this.lineCode.substring(this.cursor);
                    this.nonToken += codeFragment;
                    this.cursor += codeFragment.length;
                }

                this.lex();
            }
        }
        
        function moveToNextLine()
        {
            this.appendNonToken();
            
            var row = rows[this.lineIndex];
            var line = row ? row.cells[1] : null;
            if (line && this.newLine) {
                line.removeChildren();
                
                if (this.messageBubble)
                    this.newLine.appendChild(this.messageBubble);
                
                line.parentNode.insertBefore(this.newLine, line);
                line.parentNode.removeChild(line);
                
                this.newLine = null;
            }
            this.lineIndex++;
            if (this.lineIndex >= rowsLength && processChunkInterval) {
                clearInterval(processChunkInterval);
                this.sourceFrame.dispatchEventToListeners("syntax highlighting complete");
                return;
            }
            row = rows[this.lineIndex];
            line = row ? row.cells[1] : null;
            
            this.messageBubble = null;
            if (line.lastChild && line.lastChild.nodeType === Node.ELEMENT_NODE && line.lastChild.hasStyleClass("webkit-html-message-bubble")) {
                this.messageBubble = line.lastChild;
                line.removeChild(this.messageBubble);
            }

            this.lineCode = line.textContent;
            this.newLine = line.cloneNode(false);
            this.cursor = 0;
            if (!line)
                moveToNextLine();
        }
    },
    
    lex: function()
    {
        var token = null;
        var codeFragment = this.lineCode.substring(this.cursor);
        
        for (var i = 0; i < this.rules.length; i++) {
            var rule = this.rules[i];
            var ruleContinueStateCondition = typeof rule.continueStateCondition === "undefined" ? this.ContinueState.None : rule.continueStateCondition;
            if (this.continueState === ruleContinueStateCondition) {
                if (typeof rule.lexStateCondition !== "undefined" && this.lexState !== rule.lexStateCondition)
                    continue;
                var match = rule.pattern.exec(codeFragment);
                if (match) {
                    token = match[0];
                    if (token) {
                        if (!rule.dontAppendNonToken)
                            this.appendNonToken();
                        return rule.action.call(this, token);
                    }
                }
            }
        }
        this.nonToken += codeFragment[0];
        this.cursor++;
    },
    
    appendNonToken: function ()
    {
        if (this.nonToken.length > 0) {
            this.newLine.appendChild(document.createTextNode(this.nonToken));
            this.nonToken = "";
        }
    },
    
    syntaxHighlightNode: function(node)
    {
        this.lineCode = node.textContent;
        node.removeChildren();
        this.newLine = node;
        this.cursor = 0;
        while (true) {
            if (this.cursor >= this.lineCode.length) {
                var codeFragment = this.lineCode.substring(this.cursor);
                this.nonToken += codeFragment;
                this.cursor += codeFragment.length;
                this.appendNonToken();
                this.newLine = null;
                return;
            }

            this.lex();
        }
    }
}

WebInspector.CSSSourceSyntaxHighlighter = function(table, sourceFrame) {
    WebInspector.SourceSyntaxHighlighter.call(this, table, sourceFrame);

    this.LexState = {
        Initial: 1,
        DeclarationProperty: 2,
        DeclarationValue: 3,
        AtMedia: 4,
        AtRule: 5,
        AtKeyframes: 6
    };
    this.ContinueState = {
        None: 0,
        Comment: 1
    };
    
    this.nonToken = "";
    this.cursor = 0;
    this.lineIndex = -1;
    this.lineCode = "";
    this.newLine = null;
    this.lexState = this.LexState.Initial;
    this.continueState = this.ContinueState.None;
    
    const urlPattern = /^url\(\s*(?:(?:"(?:[^\\\"]|(?:\\[\da-f]{1,6}\s?|\.))*"|'(?:[^\\\']|(?:\\[\da-f]{1,6}\s?|\.))*')|(?:[!#$%&*-~\w]|(?:\\[\da-f]{1,6}\s?|\.))*)\s*\)/i;
    const stringPattern = /^(?:"(?:[^\\\"]|(?:\\[\da-f]{1,6}\s?|\.))*"|'(?:[^\\\']|(?:\\[\da-f]{1,6}\s?|\.))*')/i;
    const identPattern = /^-?(?:\w|(?:\\[\da-f]{1,6}\s?|\.))(?:[-\w]|(?:\\[\da-f]{1,6}\s?|\.))*/i;
    const startBlockPattern = /^{/i;
    const endBlockPattern = /^}/i;
    this.rules = [{
        pattern: /^\/\*[^\*]*\*+([^\/*][^*]*\*+)*\//i,
        action: commentAction
    }, {
        pattern: /^(?:\/\*(?:[^\*]|\*[^\/])*)/i,
        action: commentStartAction
    }, {
        pattern: /^(?:(?:[^\*]|\*[^\/])*\*+\/)/i,
        action: commentEndAction,
        continueStateCondition: this.ContinueState.Comment
    }, {
        pattern: /^.*/i,
        action: commentMiddleAction,
        continueStateCondition: this.ContinueState.Comment
    }, {
        pattern: /^(?:(?:-?(?:\w|(?:\\[\da-f]{1,6}\s?|\.))(?:[-\w]|(?:\\[\da-f]{1,6}\s?|\.))*|\*)(?:#-?(?:\w|(?:\\[\da-f]{1,6}\s?|\.))(?:[-\w]|(?:\\[\da-f]{1,6}\s?|\.))*|\.-?(?:\w|(?:\\[\da-f]{1,6}\s?|\.))(?:[-\w]|(?:\\[\da-f]{1,6}\s?|\.))*|\[\s*-?(?:\w|(?:\\[\da-f]{1,6}\s?|\.))(?:[-\w]|(?:\\[\da-f]{1,6}\s?|\.))*\s*(?:(?:=|~=|\|=)\s*(?:-?(?:\w|(?:\\[\da-f]{1,6}\s?|\.))(?:[-\w]|(?:\\[\da-f]{1,6}\s?|\.))*|(?:"(?:[^\\\"]|(?:\\[\da-f]{1,6}\s?|\.))*"|'(?:[^\\\']|(?:\\[\da-f]{1,6}\s?|\.))*'))\s*)?\]|:(?:-?(?:\w|(?:\\[\da-f]{1,6}\s?|\.))(?:[-\w]|(?:\\[\da-f]{1,6}\s?|\.))*|-?(?:\w|(?:\\[\da-f]{1,6}\s?|\.))(?:[-\w]|(?:\\[\da-f]{1,6}\s?|\.))*\(\s*(?:-?(?:\w|(?:\\[\da-f]{1,6}\s?|\.))(?:[-\w]|(?:\\[\da-f]{1,6}\s?|\.))*\s*)?\)))*|(?:#-?(?:\w|(?:\\[\da-f]{1,6}\s?|\.))(?:[-\w]|(?:\\[\da-f]{1,6}\s?|\.))*|\.-?(?:\w|(?:\\[\da-f]{1,6}\s?|\.))(?:[-\w]|(?:\\[\da-f]{1,6}\s?|\.))*|\[\s*-?(?:\w|(?:\\[\da-f]{1,6}\s?|\.))(?:[-\w]|(?:\\[\da-f]{1,6}\s?|\.))*\s*(?:(?:=|~=|\|=)\s*(?:-?(?:\w|(?:\\[\da-f]{1,6}\s?|\.))(?:[-\w]|(?:\\[\da-f]{1,6}\s?|\.))*|(?:"(?:[^\\\"]|(?:\\[\da-f]{1,6}\s?|\.))*"|'(?:[^\\\']|(?:\\[\da-f]{1,6}\s?|\.))*'))\s*)?\]|:(?:-?(?:\w|(?:\\[\da-f]{1,6}\s?|\.))(?:[-\w]|(?:\\[\da-f]{1,6}\s?|\.))*|-?(?:\w|(?:\\[\da-f]{1,6}\s?|\.))(?:[-\w]|(?:\\[\da-f]{1,6}\s?|\.))*\(\s*(?:-?(?:\w|(?:\\[\da-f]{1,6}\s?|\.))(?:[-\w]|(?:\\[\da-f]{1,6}\s?|\.))*\s*)?\)))+)/i,
        action: selectorAction,
        lexStateCondition: this.LexState.Initial
    }, {
        pattern: startBlockPattern,
        action: startRulesetBlockAction,
        lexStateCondition: this.LexState.Initial,
        dontAppendNonToken: true
    }, {
        pattern: identPattern,
        action: propertyAction,
        lexStateCondition: this.LexState.DeclarationProperty,
        dontAppendNonToken: true
    }, {
        pattern: /^:/i,
        action: declarationColonAction,
        lexStateCondition: this.LexState.DeclarationProperty,
        dontAppendNonToken: true
    }, {
        pattern: /^(?:#(?:[\da-f]{6}|[\da-f]{3})|rgba\(\s*(?:\d+|\d*\.\d+)%?\s*,\s*(?:\d+|\d*\.\d+)%?\s*,\s*(?:\d+|\d*\.\d+)%?\s*,\s*(?:\d+|\d*\.\d+)%?\s*\)|hsla\(\s*(?:\d+|\d*\.\d+)%?\s*,\s*(?:\d+|\d*\.\d+)%?\s*,\s*(?:\d+|\d*\.\d+)%?\s*,\s*(?:\d+|\d*\.\d+)%?\s*\)|rgb\(\s*(?:\d+|\d*\.\d+)%?\s*,\s*(?:\d+|\d*\.\d+)%?\s*,\s*(?:\d+|\d*\.\d+)%?\s*\)|hsl\(\s*(?:\d+|\d*\.\d+)%?\s*,\s*(?:\d+|\d*\.\d+)%?\s*,\s*(?:\d+|\d*\.\d+)%?\s*\))/i,
        action: colorAction,
        lexStateCondition: this.LexState.DeclarationValue
    }, {
        pattern: /^(?:-?(?:\d+|\d*\.\d+)(?:em|rem|__qem|ex|px|cm|mm|in|pt|pc|deg|rad|grad|turn|ms|s|Hz|kHz|%)?)/i,
        action: numvalueAction,
        lexStateCondition: this.LexState.DeclarationValue
    }, {
        pattern: urlPattern,
        action: urlAction,
        lexStateCondition: this.LexState.DeclarationValue
    }, {
        pattern: stringPattern,
        action: stringAction,
        lexStateCondition: this.LexState.DeclarationValue
    }, {
        pattern: /^!\s*important/i,
        action: importantAction,
        lexStateCondition: this.LexState.DeclarationValue
    }, {
        pattern: identPattern,
        action: valueIdentAction,
        lexStateCondition: this.LexState.DeclarationValue,
        dontAppendNonToken: true
    }, {
        pattern: /^;/i,
        action: declarationSemicolonAction,
        lexStateCondition: this.LexState.DeclarationValue,
        dontAppendNonToken: true
    }, {
        pattern: endBlockPattern,
        action: endRulesetBlockAction,
        lexStateCondition: this.LexState.DeclarationProperty,
        dontAppendNonToken: true
    }, {
        pattern: endBlockPattern,
        action: endRulesetBlockAction,
        lexStateCondition: this.LexState.DeclarationValue,
        dontAppendNonToken: true
    }, {
        pattern: /^@media/i,
        action: atMediaAction,
        lexStateCondition: this.LexState.Initial
    }, {
        pattern: startBlockPattern,
        action: startAtMediaBlockAction,
        lexStateCondition: this.LexState.AtMedia,
        dontAppendNonToken: true
    }, {
        pattern: /^@-webkit-keyframes/i,
        action: atKeyframesAction,
        lexStateCondition: this.LexState.Initial
    }, {
        pattern: startBlockPattern,
        action: startAtMediaBlockAction,
        lexStateCondition: this.LexState.AtKeyframes,
        dontAppendNonToken: true
    }, {
        pattern: /^@-?(?:\w|(?:\\[\da-f]{1,6}\s?|\.))(?:[-\w]|(?:\\[\da-f]{1,6}\s?|\.))*/i,
        action: atRuleAction,
        lexStateCondition: this.LexState.Initial
    }, {
        pattern: /^;/i,
        action: endAtRuleAction,
        lexStateCondition: this.LexState.AtRule
    }, {
        pattern: urlPattern,
        action: urlAction,
        lexStateCondition: this.LexState.AtRule
    }, {
        pattern: stringPattern,
        action: stringAction,
        lexStateCondition: this.LexState.AtRule
    }, {
        pattern: stringPattern,
        action: stringAction,
        lexStateCondition: this.LexState.AtKeyframes
    }, {
        pattern: identPattern,
        action: atRuleIdentAction,
        lexStateCondition: this.LexState.AtRule,
        dontAppendNonToken: true
    }, {
        pattern: identPattern,
        action: atRuleIdentAction,
        lexStateCondition: this.LexState.AtMedia,
        dontAppendNonToken: true
    }, {
        pattern: startBlockPattern,
        action: startAtRuleBlockAction,
        lexStateCondition: this.LexState.AtRule,
        dontAppendNonToken: true
    }];
    
    function commentAction(token)
    {
        this.cursor += token.length;
        this.newLine.appendChild(this.createSpan(token, "webkit-css-comment"));
    }
    
    function commentStartAction(token)
    {
        this.cursor += token.length;
        this.newLine.appendChild(this.createSpan(token, "webkit-css-comment"));
        this.continueState = this.ContinueState.Comment;
    }
    
    function commentEndAction(token)
    {
        this.cursor += token.length;
        this.newLine.appendChild(this.createSpan(token, "webkit-css-comment"));
        this.continueState = this.ContinueState.None;
    }

    function commentMiddleAction(token)
    {
        this.cursor += token.length;
        this.newLine.appendChild(this.createSpan(token, "webkit-css-comment"));
    }
    
    function selectorAction(token)
    {
        this.cursor += token.length;
        this.newLine.appendChild(this.createSpan(token, "webkit-css-selector"));
    }
    
    function startRulesetBlockAction(token)
    {
        this.cursor += token.length;
        this.nonToken += token;
        this.lexState = this.LexState.DeclarationProperty;
    }
    
    function endRulesetBlockAction(token)
    {
        this.cursor += token.length;
        this.nonToken += token;
        this.lexState = this.LexState.Initial;
    }
    
    const propertyKeywords = {
        "background": true,
        "background-attachment": true,
        "background-clip": true,
        "background-color": true,
        "background-image": true,
        "background-origin": true,
        "background-position": true,
        "background-position-x": true,
        "background-position-y": true,
        "background-repeat": true,
        "background-repeat-x": true,
        "background-repeat-y": true,
        "background-size": true,
        "border": true,
        "border-bottom": true,
        "border-bottom-color": true,
        "border-bottom-left-radius": true,
        "border-bottom-right-radius": true,
        "border-bottom-style": true,
        "border-bottom-width": true,
        "border-collapse": true,
        "border-color": true,
        "border-left": true,
        "border-left-color": true,
        "border-left-style": true,
        "border-left-width": true,
        "border-radius": true,
        "border-right": true,
        "border-right-color": true,
        "border-right-style": true,
        "border-right-width": true,
        "border-spacing": true,
        "border-style": true,
        "border-top": true,
        "border-top-color": true,
        "border-top-left-radius": true,
        "border-top-right-radius": true,
        "border-top-style": true,
        "border-top-width": true,
        "border-width": true,
        "bottom": true,
        "caption-side": true,
        "clear": true,
        "clip": true,
        "color": true,
        "content": true,
        "counter-increment": true,
        "counter-reset": true,
        "cursor": true,
        "direction": true,
        "display": true,
        "empty-cells": true,
        "float": true,
        "font": true,
        "font-family": true,
        "font-size": true,
        "font-stretch": true,
        "font-style": true,
        "font-variant": true,
        "font-weight": true,
        "height": true,
        "left": true,
        "letter-spacing": true,
        "line-height": true,
        "list-style": true,
        "list-style-image": true,
        "list-style-position": true,
        "list-style-type": true,
        "margin": true,
        "margin-bottom": true,
        "margin-left": true,
        "margin-right": true,
        "margin-top": true,
        "max-height": true,
        "max-width": true,
        "min-height": true,
        "min-width": true,
        "opacity": true,
        "orphans": true,
        "outline": true,
        "outline-color": true,
        "outline-offset": true,
        "outline-style": true,
        "outline-width": true,
        "overflow": true,
        "overflow-x": true,
        "overflow-y": true,
        "padding": true,
        "padding-bottom": true,
        "padding-left": true,
        "padding-right": true,
        "padding-top": true,
        "page": true,
        "page-break-after": true,
        "page-break-before": true,
        "page-break-inside": true,
        "pointer-events": true,
        "position": true,
        "quotes": true,
        "resize": true,
        "right": true,
        "size": true,
        "src": true,
        "table-layout": true,
        "text-align": true,
        "text-decoration": true,
        "text-indent": true,
        "text-line-through": true,
        "text-line-through-color": true,
        "text-line-through-mode": true,
        "text-line-through-style": true,
        "text-line-through-width": true,
        "text-overflow": true,
        "text-overline": true,
        "text-overline-color": true,
        "text-overline-mode": true,
        "text-overline-style": true,
        "text-overline-width": true,
        "text-rendering": true,
        "text-shadow": true,
        "text-transform": true,
        "text-underline": true,
        "text-underline-color": true,
        "text-underline-mode": true,
        "text-underline-style": true,
        "text-underline-width": true,
        "top": true,
        "unicode-bidi": true,
        "unicode-range": true,
        "vertical-align": true,
        "visibility": true,
        "white-space": true,
        "widows": true,
        "width": true,
        "word-break": true,
        "word-spacing": true,
        "word-wrap": true,
        "z-index": true,
        "zoom": true,
        "-webkit-animation": true,
        "-webkit-animation-delay": true,
        "-webkit-animation-direction": true,
        "-webkit-animation-duration": true,
        "-webkit-animation-iteration-count": true,
        "-webkit-animation-name": true,
        "-webkit-animation-play-state": true,
        "-webkit-animation-timing-function": true,
        "-webkit-appearance": true,
        "-webkit-backface-visibility": true,
        "-webkit-background-clip": true,
        "-webkit-background-composite": true,
        "-webkit-background-origin": true,
        "-webkit-background-size": true,
        "-webkit-binding": true,
        "-webkit-border-fit": true,
        "-webkit-border-horizontal-spacing": true,
        "-webkit-border-image": true,
        "-webkit-border-radius": true,
        "-webkit-border-vertical-spacing": true,
        "-webkit-box-align": true,
        "-webkit-box-direction": true,
        "-webkit-box-flex": true,
        "-webkit-box-flex-group": true,
        "-webkit-box-lines": true,
        "-webkit-box-ordinal-group": true,
        "-webkit-box-orient": true,
        "-webkit-box-pack": true,
        "-webkit-box-reflect": true,
        "-webkit-box-shadow": true,
        "-webkit-box-sizing": true,
        "-webkit-column-break-after": true,
        "-webkit-column-break-before": true,
        "-webkit-column-break-inside": true,
        "-webkit-column-count": true,
        "-webkit-column-gap": true,
        "-webkit-column-rule": true,
        "-webkit-column-rule-color": true,
        "-webkit-column-rule-style": true,
        "-webkit-column-rule-width": true,
        "-webkit-column-width": true,
        "-webkit-columns": true,
        "-webkit-font-size-delta": true,
        "-webkit-font-smoothing": true,
        "-webkit-highlight": true,
        "-webkit-line-break": true,
        "-webkit-line-clamp": true,
        "-webkit-margin-bottom-collapse": true,
        "-webkit-margin-collapse": true,
        "-webkit-margin-start": true,
        "-webkit-margin-top-collapse": true,
        "-webkit-marquee": true,
        "-webkit-marquee-direction": true,
        "-webkit-marquee-increment": true,
        "-webkit-marquee-repetition": true,
        "-webkit-marquee-speed": true,
        "-webkit-marquee-style": true,
        "-webkit-mask": true,
        "-webkit-mask-attachment": true,
        "-webkit-mask-box-image": true,
        "-webkit-mask-clip": true,
        "-webkit-mask-composite": true,
        "-webkit-mask-image": true,
        "-webkit-mask-origin": true,
        "-webkit-mask-position": true,
        "-webkit-mask-position-x": true,
        "-webkit-mask-position-y": true,
        "-webkit-mask-repeat": true,
        "-webkit-mask-repeat-x": true,
        "-webkit-mask-repeat-y": true,
        "-webkit-mask-size": true,
        "-webkit-match-nearest-mail-blockquote-color": true,
        "-webkit-nbsp-mode": true,
        "-webkit-padding-start": true,
        "-webkit-perspective": true,
        "-webkit-perspective-origin": true,
        "-webkit-perspective-origin-x": true,
        "-webkit-perspective-origin-y": true,
        "-webkit-rtl-ordering": true,
        "-webkit-text-decorations-in-effect": true,
        "-webkit-text-fill-color": true,
        "-webkit-text-security": true,
        "-webkit-text-size-adjust": true,
        "-webkit-text-stroke": true,
        "-webkit-text-stroke-color": true,
        "-webkit-text-stroke-width": true,
        "-webkit-transform": true,
        "-webkit-transform-origin": true,
        "-webkit-transform-origin-x": true,
        "-webkit-transform-origin-y": true,
        "-webkit-transform-origin-z": true,
        "-webkit-transform-style": true,
        "-webkit-transition": true,
        "-webkit-transition-delay": true,
        "-webkit-transition-duration": true,
        "-webkit-transition-property": true,
        "-webkit-transition-timing-function": true,
        "-webkit-user-drag": true,
        "-webkit-user-modify": true,
        "-webkit-user-select": true,
        "-webkit-variable-declaration-block": true
    };
    function propertyAction(token)
    {
        this.cursor += token.length;
        if (token in propertyKeywords) {
            this.appendNonToken.call(this);
            this.newLine.appendChild(this.createSpan(token, "webkit-css-property"));
        } else
            this.nonToken += token;
    }
    
    function declarationColonAction(token)
    {
        this.cursor += token.length;
        this.nonToken += token;
        this.lexState = this.LexState.DeclarationValue;
    }

    const valueKeywords = {
        "inherit": true,
        "initial": true,
        "none": true,
        "hidden": true,
        "inset": true,
        "groove": true,
        "ridge": true,
        "outset": true,
        "dotted": true,
        "dashed": true,
        "solid": true,
        "double": true,
        "caption": true,
        "icon": true,
        "menu": true,
        "message-box": true,
        "small-caption": true,
        "-webkit-mini-control": true,
        "-webkit-small-control": true,
        "-webkit-control": true,
        "status-bar": true,
        "italic": true,
        "oblique": true,
        "all": true,
        "small-caps": true,
        "normal": true,
        "bold": true,
        "bolder": true,
        "lighter": true,
        "xx-small": true,
        "x-small": true,
        "small": true,
        "medium": true,
        "large": true,
        "x-large": true,
        "xx-large": true,
        "-webkit-xxx-large": true,
        "smaller": true,
        "larger": true,
        "wider": true,
        "narrower": true,
        "ultra-condensed": true,
        "extra-condensed": true,
        "condensed": true,
        "semi-condensed": true,
        "semi-expanded": true,
        "expanded": true,
        "extra-expanded": true,
        "ultra-expanded": true,
        "serif": true,
        "sans-serif": true,
        "cursive": true,
        "fantasy": true,
        "monospace": true,
        "-webkit-body": true,
        "aqua": true,
        "black": true,
        "blue": true,
        "fuchsia": true,
        "gray": true,
        "green": true,
        "lime": true,
        "maroon": true,
        "navy": true,
        "olive": true,
        "orange": true,
        "purple": true,
        "red": true,
        "silver": true,
        "teal": true,
        "white": true,
        "yellow": true,
        "transparent": true,
        "-webkit-link": true,
        "-webkit-activelink": true,
        "activeborder": true,
        "activecaption": true,
        "appworkspace": true,
        "background": true,
        "buttonface": true,
        "buttonhighlight": true,
        "buttonshadow": true,
        "buttontext": true,
        "captiontext": true,
        "graytext": true,
        "highlight": true,
        "highlighttext": true,
        "inactiveborder": true,
        "inactivecaption": true,
        "inactivecaptiontext": true,
        "infobackground": true,
        "infotext": true,
        "match": true,
        "menutext": true,
        "scrollbar": true,
        "threeddarkshadow": true,
        "threedface": true,
        "threedhighlight": true,
        "threedlightshadow": true,
        "threedshadow": true,
        "window": true,
        "windowframe": true,
        "windowtext": true,
        "-webkit-focus-ring-color": true,
        "currentcolor": true,
        "grey": true,
        "-webkit-text": true,
        "repeat": true,
        "repeat-x": true,
        "repeat-y": true,
        "no-repeat": true,
        "clear": true,
        "copy": true,
        "source-over": true,
        "source-in": true,
        "source-out": true,
        "source-atop": true,
        "destination-over": true,
        "destination-in": true,
        "destination-out": true,
        "destination-atop": true,
        "xor": true,
        "plus-darker": true,
        "plus-lighter": true,
        "baseline": true,
        "middle": true,
        "sub": true,
        "super": true,
        "text-top": true,
        "text-bottom": true,
        "top": true,
        "bottom": true,
        "-webkit-baseline-middle": true,
        "-webkit-auto": true,
        "left": true,
        "right": true,
        "center": true,
        "justify": true,
        "-webkit-left": true,
        "-webkit-right": true,
        "-webkit-center": true,
        "outside": true,
        "inside": true,
        "disc": true,
        "circle": true,
        "square": true,
        "decimal": true,
        "decimal-leading-zero": true,
        "lower-roman": true,
        "upper-roman": true,
        "lower-greek": true,
        "lower-alpha": true,
        "lower-latin": true,
        "upper-alpha": true,
        "upper-latin": true,
        "hebrew": true,
        "armenian": true,
        "georgian": true,
        "cjk-ideographic": true,
        "hiragana": true,
        "katakana": true,
        "hiragana-iroha": true,
        "katakana-iroha": true,
        "inline": true,
        "block": true,
        "list-item": true,
        "run-in": true,
        "compact": true,
        "inline-block": true,
        "table": true,
        "inline-table": true,
        "table-row-group": true,
        "table-header-group": true,
        "table-footer-group": true,
        "table-row": true,
        "table-column-group": true,
        "table-column": true,
        "table-cell": true,
        "table-caption": true,
        "-webkit-box": true,
        "-webkit-inline-box": true,
        "-wap-marquee": true,
        "auto": true,
        "crosshair": true,
        "default": true,
        "pointer": true,
        "move": true,
        "vertical-text": true,
        "cell": true,
        "context-menu": true,
        "alias": true,
        "progress": true,
        "no-drop": true,
        "not-allowed": true,
        "-webkit-zoom-in": true,
        "-webkit-zoom-out": true,
        "e-resize": true,
        "ne-resize": true,
        "nw-resize": true,
        "n-resize": true,
        "se-resize": true,
        "sw-resize": true,
        "s-resize": true,
        "w-resize": true,
        "ew-resize": true,
        "ns-resize": true,
        "nesw-resize": true,
        "nwse-resize": true,
        "col-resize": true,
        "row-resize": true,
        "text": true,
        "wait": true,
        "help": true,
        "all-scroll": true,
        "-webkit-grab": true,
        "-webkit-grabbing": true,
        "ltr": true,
        "rtl": true,
        "capitalize": true,
        "uppercase": true,
        "lowercase": true,
        "visible": true,
        "collapse": true,
        "above": true,
        "absolute": true,
        "always": true,
        "avoid": true,
        "below": true,
        "bidi-override": true,
        "blink": true,
        "both": true,
        "close-quote": true,
        "crop": true,
        "cross": true,
        "embed": true,
        "fixed": true,
        "hand": true,
        "hide": true,
        "higher": true,
        "invert": true,
        "landscape": true,
        "level": true,
        "line-through": true,
        "local": true,
        "loud": true,
        "lower": true,
        "-webkit-marquee": true,
        "mix": true,
        "no-close-quote": true,
        "no-open-quote": true,
        "nowrap": true,
        "open-quote": true,
        "overlay": true,
        "overline": true,
        "portrait": true,
        "pre": true,
        "pre-line": true,
        "pre-wrap": true,
        "relative": true,
        "scroll": true,
        "separate": true,
        "show": true,
        "static": true,
        "thick": true,
        "thin": true,
        "underline": true,
        "-webkit-nowrap": true,
        "stretch": true,
        "start": true,
        "end": true,
        "reverse": true,
        "horizontal": true,
        "vertical": true,
        "inline-axis": true,
        "block-axis": true,
        "single": true,
        "multiple": true,
        "forwards": true,
        "backwards": true,
        "ahead": true,
        "up": true,
        "down": true,
        "slow": true,
        "fast": true,
        "infinite": true,
        "slide": true,
        "alternate": true,
        "read-only": true,
        "read-write": true,
        "read-write-plaintext-only": true,
        "element": true,
        "ignore": true,
        "intrinsic": true,
        "min-intrinsic": true,
        "clip": true,
        "ellipsis": true,
        "discard": true,
        "dot-dash": true,
        "dot-dot-dash": true,
        "wave": true,
        "continuous": true,
        "skip-white-space": true,
        "break-all": true,
        "break-word": true,
        "space": true,
        "after-white-space": true,
        "checkbox": true,
        "radio": true,
        "push-button": true,
        "square-button": true,
        "button": true,
        "button-bevel": true,
        "default-button": true,
        "list-button": true,
        "listbox": true,
        "listitem": true,
        "media-fullscreen-button": true,
        "media-mute-button": true,
        "media-play-button": true,
        "media-seek-back-button": true,
        "media-seek-forward-button": true,
        "media-rewind-button": true,
        "media-return-to-realtime-button": true,
        "media-slider": true,
        "media-sliderthumb": true,
        "media-volume-slider-container": true,
        "media-volume-slider": true,
        "media-volume-sliderthumb": true,
        "media-controls-background": true,
        "media-current-time-display": true,
        "media-time-remaining-display": true,
        "menulist": true,
        "menulist-button": true,
        "menulist-text": true,
        "menulist-textfield": true,
        "slider-horizontal": true,
        "slider-vertical": true,
        "sliderthumb-horizontal": true,
        "sliderthumb-vertical": true,
        "caret": true,
        "searchfield": true,
        "searchfield-decoration": true,
        "searchfield-results-decoration": true,
        "searchfield-results-button": true,
        "searchfield-cancel-button": true,
        "textfield": true,
        "textarea": true,
        "caps-lock-indicator": true,
        "round": true,
        "border": true,
        "border-box": true,
        "content": true,
        "content-box": true,
        "padding": true,
        "padding-box": true,
        "contain": true,
        "cover": true,
        "logical": true,
        "visual": true,
        "lines": true,
        "running": true,
        "paused": true,
        "flat": true,
        "preserve-3d": true,
        "ease": true,
        "linear": true,
        "ease-in": true,
        "ease-out": true,
        "ease-in-out": true,
        "document": true,
        "reset": true,
        "visiblePainted": true,
        "visibleFill": true,
        "visibleStroke": true,
        "painted": true,
        "fill": true,
        "stroke": true,
        "antialiased": true,
        "subpixel-antialiased": true,
        "optimizeSpeed": true,
        "optimizeLegibility": true,
        "geometricPrecision": true
    };
    function valueIdentAction(token) {
        this.cursor += token.length;
        if (token in valueKeywords) {
            this.appendNonToken.call(this);
            this.newLine.appendChild(this.createSpan(token, "webkit-css-keyword"));
        } else
            this.nonToken += token;
    }

    function numvalueAction(token)
    {
        this.cursor += token.length;
        this.newLine.appendChild(this.createSpan(token, "webkit-css-number"));
    }
    
    function declarationSemicolonAction(token)
    {
        this.cursor += token.length;
        this.nonToken += token;
        this.lexState = this.LexState.DeclarationProperty;
    }
    
    function urlAction(token)
    {
        this.cursor += token.length;
        this.newLine.appendChild(this.createSpan(token, "webkit-css-url"));
    }
    
    function stringAction(token)
    {
        this.cursor += token.length;
        this.newLine.appendChild(this.createSpan(token, "webkit-css-string"));
    }
    
    function colorAction(token)
    {
        this.cursor += token.length;
        this.newLine.appendChild(this.createSpan(token, "webkit-css-color"));
    }
    
    function importantAction(token)
    {
        this.cursor += token.length;
        this.newLine.appendChild(this.createSpan(token, "webkit-css-important"));
    }
    
    function atMediaAction(token)
    {
        this.cursor += token.length;
        this.newLine.appendChild(this.createSpan(token, "webkit-css-at-rule"));
        this.lexState = this.LexState.AtMedia;
    }
    
    function startAtMediaBlockAction(token)
    {
        this.cursor += token.length;
        this.nonToken += token;
        this.lexState = this.LexState.Initial;
    }
    
    function atKeyframesAction(token)
    {
        this.cursor += token.length;
        this.newLine.appendChild(this.createSpan(token, "webkit-css-at-rule"));
        this.lexState = this.LexState.AtKeyframes;
    }
    
    function startAtKeyframesBlockAction(token)
    {
        this.cursor += token.length;
        this.nonToken += token;
        this.lexState = this.LexState.Initial;
    }
    
    function atRuleAction(token) {
        this.cursor += token.length;
        this.newLine.appendChild(this.createSpan(token, "webkit-css-at-rule"));
        this.lexState = this.LexState.AtRule;
    }
    
    function endAtRuleAction(token) {
        this.cursor += token.length;
        this.nonToken += token;
        this.lexState = this.LexState.Initial;
    }
    
    function startAtRuleBlockAction(token)
    {
        this.cursor += token.length;
        this.nonToken += token;
        this.lexState = this.LexState.DeclarationProperty;
    }
    
    const mediaTypes = ["all", "aural", "braille", "embossed", "handheld", "print", "projection", "screen", "tty", "tv"];
    function atRuleIdentAction(token) {
        this.cursor += token.length;
        if (mediaTypes.indexOf(token) === -1)
            this.nonToken += token;
        else {
            this.appendNonToken.call(this);
            this.newLine.appendChild(this.createSpan(token, "webkit-css-keyword"));
        }
    }
}

WebInspector.CSSSourceSyntaxHighlighter.prototype.__proto__ = WebInspector.SourceSyntaxHighlighter.prototype;

WebInspector.JavaScriptSourceSyntaxHighlighter = function(table, sourceFrame) {
    WebInspector.SourceSyntaxHighlighter.call(this, table, sourceFrame);

    this.LexState = {
        Initial: 1,
        DivisionAllowed: 2,
    };
    this.ContinueState = {
        None: 0,
        Comment: 1,
        SingleQuoteString: 2,
        DoubleQuoteString: 3,
        RegExp: 4
    };
    
    this.nonToken = "";
    this.cursor = 0;
    this.lineIndex = -1;
    this.lineCode = "";
    this.newLine = null;
    this.lexState = this.LexState.Initial;
    this.continueState = this.ContinueState.None;
    
    this.rules = [{
        pattern: /^(?:\/\/.*)/,
        action: singleLineCommentAction
    }, {
        pattern: /^(?:\/\*(?:[^\*]|\*[^\/])*\*+\/)/,
        action: multiLineSingleLineCommentAction
    }, {
        pattern: /^(?:\/\*(?:[^\*]|\*[^\/])*)/,
        action: multiLineCommentStartAction
    }, {
        pattern: /^(?:(?:[^\*]|\*[^\/])*\*+\/)/,
        action: multiLineCommentEndAction,
        continueStateCondition: this.ContinueState.Comment
    }, {
        pattern: /^.*/,
        action: multiLineCommentMiddleAction,
        continueStateCondition: this.ContinueState.Comment
    }, {
        pattern: /^(?:(?:0|[1-9]\d*)\.\d+?(?:[eE](?:\d+|\+\d+|-\d+))?|\.\d+(?:[eE](?:\d+|\+\d+|-\d+))?|(?:0|[1-9]\d*)(?:[eE](?:\d+|\+\d+|-\d+))?|0x[0-9a-fA-F]+|0X[0-9a-fA-F]+)/,
        action: numericLiteralAction
    }, {
        pattern: /^(?:"(?:[^"\\]|\\(?:['"\bfnrtv]|[^'"\bfnrtv0-9xu]|0|x[0-9a-fA-F][0-9a-fA-F]|(?:u[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])))*"|'(?:[^'\\]|\\(?:['"\bfnrtv]|[^'"\bfnrtv0-9xu]|0|x[0-9a-fA-F][0-9a-fA-F]|(?:u[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])))*')/,
        action: stringLiteralAction
    }, {
        pattern: /^(?:'(?:[^'\\]|\\(?:['"\bfnrtv]|[^'"\bfnrtv0-9xu]|0|x[0-9a-fA-F][0-9a-fA-F]|(?:u[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])))*)\\$/,
        action: singleQuoteStringStartAction
    }, {
        pattern: /^(?:(?:[^'\\]|\\(?:['"\bfnrtv]|[^'"\bfnrtv0-9xu]|0|x[0-9a-fA-F][0-9a-fA-F]|(?:u[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])))*')/,
        action: singleQuoteStringEndAction,
        continueStateCondition: this.ContinueState.SingleQuoteString
    }, {
        pattern: /^(?:(?:[^'\\]|\\(?:['"\bfnrtv]|[^'"\bfnrtv0-9xu]|0|x[0-9a-fA-F][0-9a-fA-F]|(?:u[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])))*)\\$/,
        action: singleQuoteStringMiddleAction,
        continueStateCondition: this.ContinueState.SingleQuoteString
    }, {
        pattern: /^(?:"(?:[^"\\]|\\(?:['"\bfnrtv]|[^'"\bfnrtv0-9xu]|0|x[0-9a-fA-F][0-9a-fA-F]|(?:u[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])))*)\\$/,
        action: doubleQuoteStringStartAction
    }, {
        pattern: /^(?:(?:[^"\\]|\\(?:['"\bfnrtv]|[^'"\bfnrtv0-9xu]|0|x[0-9a-fA-F][0-9a-fA-F]|(?:u[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])))*")/,
        action: doubleQuoteStringEndAction,
        continueStateCondition: this.ContinueState.DoubleQuoteString
    }, {
        pattern: /^(?:(?:[^"\\]|\\(?:['"\bfnrtv]|[^'"\bfnrtv0-9xu]|0|x[0-9a-fA-F][0-9a-fA-F]|(?:u[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])))*)\\$/,
        action: doubleQuoteStringMiddleAction,
        continueStateCondition: this.ContinueState.DoubleQuoteString
    }, {
        pattern: /^(?:(?:[a-zA-Z]|[$_]|\\(?:u[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]))(?:(?:[a-zA-Z]|[$_]|\\(?:u[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]))|[0-9])*)/,
        action: identOrKeywordAction
    }, {
        pattern: /^\)/,
        action: rightParenAction,
        dontAppendNonToken: true
    }, {
        pattern: /^(?:<=|>=|===|==|!=|!==|\+\+|\-\-|<<|>>|>>>|&&|\|\||\+=|\-=|\*=|%=|<<=|>>=|>>>=|&=|\|=|^=|[{}\(\[\]\.;,<>\+\-\*%&\|\^!~\?:=])/,
        action: punctuatorAction,
        dontAppendNonToken: true
    }, {
        pattern: /^(?:\/=?)/,
        action: divPunctuatorAction,
        lexStateCondition: this.LexState.DivisionAllowed,
        dontAppendNonToken: true
    }, {
        pattern: /^(?:\/(?:(?:\\.)|[^\\*\/])(?:(?:\\.)|[^\\/])*\/(?:(?:[a-zA-Z]|[$_]|\\(?:u[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]))|[0-9])*)/,
        action: regExpLiteralAction
    }, {
        pattern: /^(?:\/(?:(?:\\.)|[^\\*\/])(?:(?:\\.)|[^\\/])*)\\$/,
        action: regExpStartAction
    }, {
        pattern: /^(?:(?:(?:\\.)|[^\\/])*\/(?:(?:[a-zA-Z]|[$_]|\\(?:u[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]))|[0-9])*)/,
        action: regExpEndAction,
        continueStateCondition: this.ContinueState.RegExp
    }, {
        pattern: /^(?:(?:(?:\\.)|[^\\/])*)\\$/,
        action: regExpMiddleAction,
        continueStateCondition: this.ContinueState.RegExp
    }];
    
    function singleLineCommentAction(token)
    {
        this.cursor += token.length;
        this.newLine.appendChild(this.createSpan(token, "webkit-javascript-comment"));
    }
    
    function multiLineSingleLineCommentAction(token)
    {
        this.cursor += token.length;
        this.newLine.appendChild(this.createSpan(token, "webkit-javascript-comment"));
    }
    
    function multiLineCommentStartAction(token)
    {
        this.cursor += token.length;
        this.newLine.appendChild(this.createSpan(token, "webkit-javascript-comment"));
        this.continueState = this.ContinueState.Comment;
    }
    
    function multiLineCommentEndAction(token)
    {
        this.cursor += token.length;
        this.newLine.appendChild(this.createSpan(token, "webkit-javascript-comment"));
        this.continueState = this.ContinueState.None;
    }
    
    function multiLineCommentMiddleAction(token)
    {
        this.cursor += token.length;
        this.newLine.appendChild(this.createSpan(token, "webkit-javascript-comment"));
    }
    
    function numericLiteralAction(token)
    {
        this.cursor += token.length;
        this.newLine.appendChild(this.createSpan(token, "webkit-javascript-number"));
        this.lexState = this.LexState.DivisionAllowed;
    }
    
    function stringLiteralAction(token)
    {
        this.cursor += token.length;
        this.newLine.appendChild(this.createSpan(token, "webkit-javascript-string"));
        this.lexState = this.LexState.Initial;
    }
    
    function singleQuoteStringStartAction(token)
    {
        this.cursor += token.length;
        this.newLine.appendChild(this.createSpan(token, "webkit-javascript-string"));
        this.continueState = this.ContinueState.SingleQuoteString;
    }
    
    function singleQuoteStringEndAction(token)
    {
        this.cursor += token.length;
        this.newLine.appendChild(this.createSpan(token, "webkit-javascript-string"));
        this.continueState = this.ContinueState.None;
    }
    
    function singleQuoteStringMiddleAction(token)
    {
        this.cursor += token.length;
        this.newLine.appendChild(this.createSpan(token, "webkit-javascript-string"));
    }
    
    function doubleQuoteStringStartAction(token)
    {
        this.cursor += token.length;
        this.newLine.appendChild(this.createSpan(token, "webkit-javascript-string"));
        this.continueState = this.ContinueState.DoubleQuoteString;
    }
    
    function doubleQuoteStringEndAction(token)
    {
        this.cursor += token.length;
        this.newLine.appendChild(this.createSpan(token, "webkit-javascript-string"));
        this.continueState = this.ContinueState.None;
    }
    
    function doubleQuoteStringMiddleAction(token)
    {
        this.cursor += token.length;
        this.newLine.appendChild(this.createSpan(token, "webkit-javascript-string"));
    }
    
    function regExpLiteralAction(token)
    {
        this.cursor += token.length;
        this.newLine.appendChild(this.createSpan(token, "webkit-javascript-regexp"));
        this.lexState = this.LexState.Initial;
    }

    function regExpStartAction(token)
    {
        this.cursor += token.length;
        this.newLine.appendChild(this.createSpan(token, "webkit-javascript-regexp"));
        this.continueState = this.ContinueState.RegExp;
    }

    function regExpEndAction(token)
    {
        this.cursor += token.length;
        this.newLine.appendChild(this.createSpan(token, "webkit-javascript-regexp"));
        this.continueState = this.ContinueState.None;
    }

    function regExpMiddleAction(token)
    {
        this.cursor += token.length;
        this.newLine.appendChild(this.createSpan(token, "webkit-javascript-regexp"));
    }
    
    const keywords = {
        "null": true,
        "true": true,
        "false": true,
        "break": true,
        "case": true,
        "catch": true,
        "const": true,
        "default": true,
        "finally": true,
        "for": true,
        "instanceof": true,
        "new": true,
        "var": true,
        "continue": true,
        "function": true,
        "return": true,
        "void": true,
        "delete": true,
        "if": true,
        "this": true,
        "do": true,
        "while": true,
        "else": true,
        "in": true,
        "switch": true,
        "throw": true,
        "try": true,
        "typeof": true,
        "debugger": true,
        "class": true,
        "enum": true,
        "export": true,
        "extends": true,
        "import": true,
        "super": true,
        "get": true,
        "set": true
    };
    function identOrKeywordAction(token)
    {
        this.cursor += token.length;
        
        if (token in keywords) {
            this.newLine.appendChild(this.createSpan(token, "webkit-javascript-keyword"));
            this.lexState = this.LexState.Initial;
        } else {
            var identElement = this.createSpan(token, "webkit-javascript-ident");
            identElement.addEventListener("mouseover", showDatatip, false);
            this.newLine.appendChild(identElement);
            this.lexState = this.LexState.DivisionAllowed;
        }
    }
    
    function showDatatip(event) {
        if (!WebInspector.panels.scripts || !WebInspector.panels.scripts.paused)
            return;

        var node = event.target;
        var parts = [node.textContent];
        while (node.previousSibling && node.previousSibling.textContent === ".") {
            node = node.previousSibling.previousSibling;
            if (!node || !node.hasStyleClass("webkit-javascript-ident"))
                break;
            parts.unshift(node.textContent);
        }

        var expression = parts.join(".");

        WebInspector.panels.scripts.evaluateInSelectedCallFrame(expression, false, "console", callback);
        function callback(result, exception)
        {
            if (exception)
                return;
            event.target.setAttribute("title", result.description);
            event.target.addEventListener("mouseout", onmouseout, false);
            
            function onmouseout(event)
            {
                event.target.removeAttribute("title");
                event.target.removeEventListener("mouseout", onmouseout, false);
            }
        }
    }
    
    function divPunctuatorAction(token)
    {
        this.cursor += token.length;
        this.nonToken += token;
        this.lexState = this.LexState.Initial;
    }
    
    function rightParenAction(token)
    {
        this.cursor += token.length;
        this.nonToken += token;
        this.lexState = this.LexState.DivisionAllowed;
    }
    
    function punctuatorAction(token)
    {
        this.cursor += token.length;
        this.nonToken += token;
        this.lexState = this.LexState.Initial;
    }
}

WebInspector.JavaScriptSourceSyntaxHighlighter.prototype.__proto__ = WebInspector.SourceSyntaxHighlighter.prototype;

/* SourceView.js */

/*
 * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.SourceView = function(resource)
{
    // Set the sourceFrame first since WebInspector.ResourceView will set headersVisible
    // and our override of headersVisible needs the sourceFrame.
    this.sourceFrame = new WebInspector.SourceFrame(null, this._addBreakpoint.bind(this));

    WebInspector.ResourceView.call(this, resource);

    resource.addEventListener("finished", this._resourceLoadingFinished, this);

    this.element.addStyleClass("source");

    this._frameNeedsSetup = true;

    this.contentElement.appendChild(this.sourceFrame.element);

    var gutterElement = document.createElement("div");
    gutterElement.className = "webkit-line-gutter-backdrop";
    this.element.appendChild(gutterElement);
}

WebInspector.SourceView.prototype = {
    set headersVisible(x)
    {
        if (x === this._headersVisible)
            return;

        var superSetter = WebInspector.ResourceView.prototype.__lookupSetter__("headersVisible");
        if (superSetter)
            superSetter.call(this, x);

        this.sourceFrame.autoSizesToFitContentHeight = x;
    },

    show: function(parentElement)
    {
        WebInspector.ResourceView.prototype.show.call(this, parentElement);
        this.setupSourceFrameIfNeeded();
    },

    hide: function()
    {
        WebInspector.View.prototype.hide.call(this);
        this._currentSearchResultIndex = -1;
    },

    resize: function()
    {
        if (this.sourceFrame.autoSizesToFitContentHeight)
            this.sourceFrame.sizeToFitContentHeight();
    },

    detach: function()
    {
        WebInspector.ResourceView.prototype.detach.call(this);

        // FIXME: We need to mark the frame for setup on detach because the frame DOM is cleared
        // when it is removed from the document. Is this a bug?
        this._frameNeedsSetup = true;
        this._sourceFrameSetup = false;
    },

    setupSourceFrameIfNeeded: function()
    {
        if (!this._frameNeedsSetup)
            return;

        this.attach();

        delete this._frameNeedsSetup;
        this.sourceFrame.addEventListener("content loaded", this._contentLoaded, this);
        InspectorController.addResourceSourceToFrame(this.resource.identifier, this.sourceFrame.element);
    },
    
    _contentLoaded: function()
    {
        delete this._frameNeedsSetup;
        this.sourceFrame.removeEventListener("content loaded", this._contentLoaded, this);
        
        if (this.resource.type === WebInspector.Resource.Type.Script
            || this.resource.mimeType === "application/json"
            || this.resource.mimeType === "application/javascript"
            || /\.js(on)?$/.test(this.resource.lastPathComponent) ) {
            this.sourceFrame.addEventListener("syntax highlighting complete", this._syntaxHighlightingComplete, this);
            this.sourceFrame.syntaxHighlightJavascript();
        } else if (this.resource.type === WebInspector.Resource.Type.Stylesheet
            || this.resource.mimeType === "text/css"
            || /\.css$/.test(this.resource.lastPathComponent) ) {
            this.sourceFrame.addEventListener("syntax highlighting complete", this._syntaxHighlightingComplete, this);
            this.sourceFrame.syntaxHighlightCSS();
        } else
            this._sourceFrameSetupFinished();
    },

    _resourceLoadingFinished: function(event)
    {
        this._frameNeedsSetup = true;
        this._sourceFrameSetup = false;
        if (this.visible)
            this.setupSourceFrameIfNeeded();
        this.resource.removeEventListener("finished", this._resourceLoadingFinished, this);
    },

    _addBreakpoint: function(line)
    {
        var sourceID = null;
        var closestStartingLine = 0;
        var scripts = this.resource.scripts;
        for (var i = 0; i < scripts.length; ++i) {
            var script = scripts[i];
            if (script.startingLine <= line && script.startingLine >= closestStartingLine) {
                closestStartingLine = script.startingLine;
                sourceID = script.sourceID;
            }
        }

        if (WebInspector.panels.scripts) {
            var breakpoint = new WebInspector.Breakpoint(this.resource.url, line, sourceID);
            WebInspector.panels.scripts.addBreakpoint(breakpoint);
        }
    },

    // The rest of the methods in this prototype need to be generic enough to work with a ScriptView.
    // The ScriptView prototype pulls these methods into it's prototype to avoid duplicate code.

    searchCanceled: function()
    {
        this._currentSearchResultIndex = -1;
        this._searchResults = [];
        delete this._delayedFindSearchMatches;
    },

    performSearch: function(query, finishedCallback)
    {
        // Call searchCanceled since it will reset everything we need before doing a new search.
        this.searchCanceled();

        var lineQueryRegex = /(^|\s)(?:#|line:\s*)(\d+)(\s|$)/i;
        var lineQueryMatch = query.match(lineQueryRegex);
        if (lineQueryMatch) {
            var lineToSearch = parseInt(lineQueryMatch[2]);

            // If there was a space before and after the line query part, replace with a space.
            // Otherwise replace with an empty string to eat the prefix or postfix space.
            var lineQueryReplacement = (lineQueryMatch[1] && lineQueryMatch[3] ? " " : "");
            var filterlessQuery = query.replace(lineQueryRegex, lineQueryReplacement);
        }

        this._searchFinishedCallback = finishedCallback;

        function findSearchMatches(query, finishedCallback)
        {
            if (isNaN(lineToSearch)) {
                // Search the whole document since there was no line to search.
                this._searchResults = (InspectorController.search(this.sourceFrame.element.contentDocument, query) || []);
            } else {
                var sourceRow = this.sourceFrame.sourceRow(lineToSearch);
                if (sourceRow) {
                    if (filterlessQuery) {
                        // There is still a query string, so search for that string in the line.
                        this._searchResults = (InspectorController.search(sourceRow, filterlessQuery) || []);
                    } else {
                        // Match the whole line, since there was no remaining query string to match.
                        var rowRange = this.sourceFrame.element.contentDocument.createRange();
                        rowRange.selectNodeContents(sourceRow);
                        this._searchResults = [rowRange];
                    }
                }

                // Attempt to search for the whole query, just incase it matches a color like "#333".
                var wholeQueryMatches = InspectorController.search(this.sourceFrame.element.contentDocument, query);
                if (wholeQueryMatches)
                    this._searchResults = this._searchResults.concat(wholeQueryMatches);
            }

            if (this._searchResults)
                finishedCallback(this, this._searchResults.length);
        }

        if (!this._sourceFrameSetup) {
            // The search is performed in _sourceFrameSetupFinished by calling _delayedFindSearchMatches.
            this._delayedFindSearchMatches = findSearchMatches.bind(this, query, finishedCallback);
            this.setupSourceFrameIfNeeded();
            return;
        }

        findSearchMatches.call(this, query, finishedCallback);
    },

    jumpToFirstSearchResult: function()
    {
        if (!this._searchResults || !this._searchResults.length)
            return;
        this._currentSearchResultIndex = 0;
        this._jumpToSearchResult(this._currentSearchResultIndex);
    },

    jumpToLastSearchResult: function()
    {
        if (!this._searchResults || !this._searchResults.length)
            return;
        this._currentSearchResultIndex = (this._searchResults.length - 1);
        this._jumpToSearchResult(this._currentSearchResultIndex);
    },

    jumpToNextSearchResult: function()
    {
        if (!this._searchResults || !this._searchResults.length)
            return;
        if (++this._currentSearchResultIndex >= this._searchResults.length)
            this._currentSearchResultIndex = 0;
        this._jumpToSearchResult(this._currentSearchResultIndex);
    },

    jumpToPreviousSearchResult: function()
    {
        if (!this._searchResults || !this._searchResults.length)
            return;
        if (--this._currentSearchResultIndex < 0)
            this._currentSearchResultIndex = (this._searchResults.length - 1);
        this._jumpToSearchResult(this._currentSearchResultIndex);
    },

    showingFirstSearchResult: function()
    {
        return (this._currentSearchResultIndex === 0);
    },

    showingLastSearchResult: function()
    {
        return (this._searchResults && this._currentSearchResultIndex === (this._searchResults.length - 1));
    },

    revealLine: function(lineNumber)
    {
        this.setupSourceFrameIfNeeded();
        this.sourceFrame.revealLine(lineNumber);
    },

    highlightLine: function(lineNumber)
    {
        this.setupSourceFrameIfNeeded();
        this.sourceFrame.highlightLine(lineNumber);
    },

    addMessage: function(msg)
    {
        this.sourceFrame.addMessage(msg);
    },

    clearMessages: function()
    {
        this.sourceFrame.clearMessages();
    },

    _jumpToSearchResult: function(index)
    {
        var foundRange = this._searchResults[index];
        if (!foundRange)
            return;

        var selection = this.sourceFrame.element.contentWindow.getSelection();
        selection.removeAllRanges();
        selection.addRange(foundRange);

        if (foundRange.startContainer.scrollIntoViewIfNeeded)
            foundRange.startContainer.scrollIntoViewIfNeeded(true);
        else if (foundRange.startContainer.parentNode)
            foundRange.startContainer.parentNode.scrollIntoViewIfNeeded(true);
    },

    _sourceFrameSetupFinished: function()
    {
        this._sourceFrameSetup = true;
        if (this._delayedFindSearchMatches) {
            this._delayedFindSearchMatches();
            delete this._delayedFindSearchMatches;
        }
    },

    _syntaxHighlightingComplete: function(event)
    {
        this._sourceFrameSetupFinished();
        this.sourceFrame.removeEventListener("syntax highlighting complete", null, this);
    }
}

WebInspector.SourceView.prototype.__proto__ = WebInspector.ResourceView.prototype;

/* FontView.js */

/*
 * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.FontView = function(resource)
{
    WebInspector.ResourceView.call(this, resource);

    this.element.addStyleClass("font");

    var uniqueFontName = "WebInspectorFontPreview" + this.resource.identifier;

    this.fontStyleElement = document.createElement("style");
    this.fontStyleElement.textContent = "@font-face { font-family: \"" + uniqueFontName + "\"; src: url(" + this.resource.url + "); }";
    document.getElementsByTagName("head").item(0).appendChild(this.fontStyleElement);

    this.fontPreviewElement = document.createElement("div");
    this.fontPreviewElement.className = "preview";
    this.contentElement.appendChild(this.fontPreviewElement);

    this.fontPreviewElement.style.setProperty("font-family", uniqueFontName, null);
    this.fontPreviewElement.innerHTML = "ABCDEFGHIJKLM<br>NOPQRSTUVWXYZ<br>abcdefghijklm<br>nopqrstuvwxyz<br>1234567890";

    this.updateFontPreviewSize();
}

WebInspector.FontView.prototype = {
    show: function(parentElement)
    {
        WebInspector.ResourceView.prototype.show.call(this, parentElement);
        this.updateFontPreviewSize();
    },

    resize: function()
    {
        this.updateFontPreviewSize();
    },

    updateFontPreviewSize: function ()
    {
        if (!this.fontPreviewElement || !this.visible)
            return;

        this.fontPreviewElement.removeStyleClass("preview");

        var measureFontSize = 50;
        this.fontPreviewElement.style.setProperty("position", "absolute", null);
        this.fontPreviewElement.style.setProperty("font-size", measureFontSize + "px", null);
        this.fontPreviewElement.style.removeProperty("height");

        var height = this.fontPreviewElement.offsetHeight;
        var width = this.fontPreviewElement.offsetWidth;

        var containerWidth = this.contentElement.offsetWidth;

        // Subtract some padding. This should match the padding in the CSS plus room for the scrollbar.
        containerWidth -= 40;

        if (!height || !width || !containerWidth) {
            this.fontPreviewElement.style.removeProperty("font-size");
            this.fontPreviewElement.style.removeProperty("position");
            this.fontPreviewElement.addStyleClass("preview");
            return;
        }

        var lineCount = this.fontPreviewElement.getElementsByTagName("br").length + 1;
        var realLineHeight = Math.floor(height / lineCount);
        var fontSizeLineRatio = measureFontSize / realLineHeight;
        var widthRatio = containerWidth / width;
        var finalFontSize = Math.floor(realLineHeight * widthRatio * fontSizeLineRatio) - 1;

        this.fontPreviewElement.style.setProperty("font-size", finalFontSize + "px", null);
        this.fontPreviewElement.style.setProperty("height", this.fontPreviewElement.offsetHeight + "px", null);
        this.fontPreviewElement.style.removeProperty("position");

        this.fontPreviewElement.addStyleClass("preview");
    }
}

WebInspector.FontView.prototype.__proto__ = WebInspector.ResourceView.prototype;

/* ImageView.js */

/*
 * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.ImageView = function(resource)
{
    WebInspector.ResourceView.call(this, resource);

    this.element.addStyleClass("image");

    var container = document.createElement("div");
    container.className = "image";
    this.contentElement.appendChild(container);

    this.imagePreviewElement = document.createElement("img");
    this.imagePreviewElement.addStyleClass("resource-image-view");
    this.imagePreviewElement.setAttribute("src", this.resource.url);

    container.appendChild(this.imagePreviewElement);

    container = document.createElement("div");
    container.className = "info";
    this.contentElement.appendChild(container);

    var imageNameElement = document.createElement("h1");
    imageNameElement.className = "title";
    imageNameElement.textContent = this.resource.displayName;
    container.appendChild(imageNameElement);

    var infoListElement = document.createElement("dl");
    infoListElement.className = "infoList";

    var imageProperties = [
        { name: WebInspector.UIString("Dimensions"), value: WebInspector.UIString("%d × %d", this.imagePreviewElement.naturalWidth, this.imagePreviewElement.height) },
        { name: WebInspector.UIString("File size"), value: Number.bytesToString(this.resource.contentLength, WebInspector.UIString.bind(WebInspector)) },
        { name: WebInspector.UIString("MIME type"), value: this.resource.mimeType }
    ];

    var listHTML = '';
    for (var i = 0; i < imageProperties.length; ++i)
        listHTML += "<dt>" + imageProperties[i].name + "</dt><dd>" + imageProperties[i].value + "</dd>";

    infoListElement.innerHTML = listHTML;
    container.appendChild(infoListElement);
}

WebInspector.ImageView.prototype = {
    
}

WebInspector.ImageView.prototype.__proto__ = WebInspector.ResourceView.prototype;

/* DatabaseTableView.js */

/*
 * Copyright (C) 2008 Apple Inc. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.DatabaseTableView = function(database, tableName)
{
    WebInspector.View.call(this);

    this.database = database;
    this.tableName = tableName;

    this.element.addStyleClass("storage-view");
    this.element.addStyleClass("table");

    this.refreshButton = new WebInspector.StatusBarButton(WebInspector.UIString("Refresh"), "refresh-storage-status-bar-item");
    this.refreshButton.addEventListener("click", this._refreshButtonClicked.bind(this), false);
}

WebInspector.DatabaseTableView.prototype = {
    show: function(parentElement)
    {
        WebInspector.View.prototype.show.call(this, parentElement);
        this.update();
    },

    get statusBarItems()
    {
        return [this.refreshButton];
    },

    update: function()
    {
        this.database.executeSql("SELECT * FROM " + this.tableName, this._queryFinished.bind(this), this._queryError.bind(this));
    },

    _queryFinished: function(result)
    {
        this.element.removeChildren();

        var dataGrid = WebInspector.panels.storage.dataGridForResult(result);
        if (!dataGrid) {
            var emptyMsgElement = document.createElement("div");
            emptyMsgElement.className = "storage-table-empty";
            emptyMsgElement.textContent = WebInspector.UIString("The “%s”\ntable is empty.", this.tableName);
            this.element.appendChild(emptyMsgElement);
            return;
        }

        this.element.appendChild(dataGrid.element);
    },

    _queryError: function(error)
    {
        this.element.removeChildren();

        var errorMsgElement = document.createElement("div");
        errorMsgElement.className = "storage-table-error";
        errorMsgElement.textContent = WebInspector.UIString("An error occurred trying to\nread the “%s” table.", this.tableName);
        this.element.appendChild(errorMsgElement);
    },

    _refreshButtonClicked: function(event)
    {
        this.update();
    }
}

WebInspector.DatabaseTableView.prototype.__proto__ = WebInspector.View.prototype;

/* DatabaseQueryView.js */

/*
 * Copyright (C) 2008 Apple Inc. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.DatabaseQueryView = function(database)
{
    WebInspector.View.call(this);

    this.database = database;

    this.element.addStyleClass("storage-view");
    this.element.addStyleClass("query");
    this.element.tabIndex = 0;

    this.element.addEventListener("selectstart", this._selectStart.bind(this), false);

    this.promptElement = document.createElement("div");
    this.promptElement.className = "database-query-prompt";
    this.promptElement.appendChild(document.createElement("br"));
    this.promptElement.handleKeyEvent = this._promptKeyDown.bind(this);
    this.element.appendChild(this.promptElement);

    this.prompt = new WebInspector.TextPrompt(this.promptElement, this.completions.bind(this), " ");
}

WebInspector.DatabaseQueryView.prototype = {
    show: function(parentElement)
    {
        WebInspector.View.prototype.show.call(this, parentElement);

        function moveBackIfOutside()
        {
            if (!this.prompt.isCaretInsidePrompt() && window.getSelection().isCollapsed)
                this.prompt.moveCaretToEndOfPrompt();
        }

        setTimeout(moveBackIfOutside.bind(this), 0);
    },

    completions: function(wordRange, bestMatchOnly, completionsReadyCallback)
    {
        var prefix = wordRange.toString().toLowerCase();
        if (!prefix.length)
            return;

        var results = [];

        function accumulateMatches(textArray)
        {
            if (bestMatchOnly && results.length)
                return;
            for (var i = 0; i < textArray.length; ++i) {
                var text = textArray[i].toLowerCase();
                if (text.length < prefix.length)
                    continue;
                if (text.indexOf(prefix) !== 0)
                    continue;
                results.push(textArray[i]);
                if (bestMatchOnly)
                    return;
            }
        }
        
        function tableNamesCallback(tableNames)
        {
            accumulateMatches(tableNames.map(function(name) { return name + " " }));
            accumulateMatches(["SELECT ", "FROM ", "WHERE ", "LIMIT ", "DELETE FROM ", "CREATE ", "DROP ", "TABLE ", "INDEX ", "UPDATE ", "INSERT INTO ", "VALUES ("]);

            completionsReadyCallback(results);
        }
        this.database.getTableNames(tableNamesCallback);
    },

    _promptKeyDown: function(event)
    {
        if (isEnterKey(event)) {
            this._enterKeyPressed(event);
            return;
        }

        this.prompt.handleKeyEvent(event);
    },

    _selectStart: function(event)
    {
        if (this._selectionTimeout)
            clearTimeout(this._selectionTimeout);

        this.prompt.clearAutoComplete();

        function moveBackIfOutside()
        {
            delete this._selectionTimeout;
            if (!this.prompt.isCaretInsidePrompt() && window.getSelection().isCollapsed)
                this.prompt.moveCaretToEndOfPrompt();
            this.prompt.autoCompleteSoon();
        }

        this._selectionTimeout = setTimeout(moveBackIfOutside.bind(this), 100);
    },

    _enterKeyPressed: function(event)
    {
        event.preventDefault();
        event.stopPropagation();

        this.prompt.clearAutoComplete(true);

        var query = this.prompt.text;
        if (!query.length)
            return;

        this.prompt.history.push(query);
        this.prompt.historyOffset = 0;
        this.prompt.text = "";

        this.database.executeSql(query, this._queryFinished.bind(this, query), this._queryError.bind(this, query));
    },

    _queryFinished: function(query, result)
    {
        var dataGrid = WebInspector.panels.storage.dataGridForResult(result);
        if (!dataGrid)
            return;
        dataGrid.element.addStyleClass("inline");
        this._appendQueryResult(query, dataGrid.element);

        if (query.match(/^create /i) || query.match(/^drop table /i))
            WebInspector.panels.storage.updateDatabaseTables(this.database);
    },

    _queryError: function(query, error)
    {
        if (error.code == 1)
            var message = error.message;
        else if (error.code == 2)
            var message = WebInspector.UIString("Database no longer has expected version.");
        else
            var message = WebInspector.UIString("An unexpected error %s occurred.", error.code);

        this._appendQueryResult(query, message, "error");
    },

    _appendQueryResult: function(query, result, resultClassName)
    {
        var element = document.createElement("div");
        element.className = "database-user-query";

        var commandTextElement = document.createElement("span");
        commandTextElement.className = "database-query-text";
        commandTextElement.textContent = query;
        element.appendChild(commandTextElement);

        var resultElement = document.createElement("div");
        resultElement.className = "database-query-result";

        if (resultClassName)
            resultElement.addStyleClass(resultClassName);

        if (typeof result === "string" || result instanceof String)
            resultElement.textContent = result;
        else if (result && result.nodeName)
            resultElement.appendChild(result);

        if (resultElement.childNodes.length)
            element.appendChild(resultElement);

        this.element.insertBefore(element, this.promptElement);
        this.promptElement.scrollIntoView(false);
    }
}

WebInspector.DatabaseQueryView.prototype.__proto__ = WebInspector.View.prototype;

/* ScriptView.js */

/*
 * Copyright (C) 2008 Apple Inc. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.ScriptView = function(script)
{
    WebInspector.View.call(this);

    this.element.addStyleClass("script-view");

    this.script = script;

    this._frameNeedsSetup = true;
    this._sourceFrameSetup = false;

    this.sourceFrame = new WebInspector.SourceFrame(null, this._addBreakpoint.bind(this));

    this.element.appendChild(this.sourceFrame.element);
}

WebInspector.ScriptView.prototype = {
    show: function(parentElement)
    {
        WebInspector.View.prototype.show.call(this, parentElement);
        this.setupSourceFrameIfNeeded();
    },

    hide: function()
    {
        WebInspector.View.prototype.hide.call(this);
        this._currentSearchResultIndex = -1;
    },

    setupSourceFrameIfNeeded: function()
    {
        if (!this._frameNeedsSetup)
            return;

        this.attach();

        if (!InspectorController.addSourceToFrame("text/javascript", this.script.source, this.sourceFrame.element))
            return;

        delete this._frameNeedsSetup;

        this.sourceFrame.addEventListener("syntax highlighting complete", this._syntaxHighlightingComplete, this);
        this.sourceFrame.syntaxHighlightJavascript();
    },

    attach: function()
    {
        if (!this.element.parentNode)
            document.getElementById("script-resource-views").appendChild(this.element);
    },

    _addBreakpoint: function(line)
    {
        var breakpoint = new WebInspector.Breakpoint(this.script.sourceURL, line, this.script.sourceID);
        WebInspector.panels.scripts.addBreakpoint(breakpoint);
    },

    // The follow methods are pulled from SourceView, since they are
    // generic and work with ScriptView just fine.

    revealLine: WebInspector.SourceView.prototype.revealLine,
    highlightLine: WebInspector.SourceView.prototype.highlightLine,
    addMessage: WebInspector.SourceView.prototype.addMessage,
    clearMessages: WebInspector.SourceView.prototype.clearMessages,
    searchCanceled: WebInspector.SourceView.prototype.searchCanceled,
    performSearch: WebInspector.SourceView.prototype.performSearch,
    jumpToFirstSearchResult: WebInspector.SourceView.prototype.jumpToFirstSearchResult,
    jumpToLastSearchResult: WebInspector.SourceView.prototype.jumpToLastSearchResult,
    jumpToNextSearchResult: WebInspector.SourceView.prototype.jumpToNextSearchResult,
    jumpToPreviousSearchResult: WebInspector.SourceView.prototype.jumpToPreviousSearchResult,
    showingFirstSearchResult: WebInspector.SourceView.prototype.showingFirstSearchResult,
    showingLastSearchResult: WebInspector.SourceView.prototype.showingLastSearchResult,
    _jumpToSearchResult: WebInspector.SourceView.prototype._jumpToSearchResult,
    _sourceFrameSetupFinished: WebInspector.SourceView.prototype._sourceFrameSetupFinished,
    _syntaxHighlightingComplete: WebInspector.SourceView.prototype._syntaxHighlightingComplete
}

WebInspector.ScriptView.prototype.__proto__ = WebInspector.View.prototype;

/* ProfileDataGridTree.js */

/*
 * Copyright (C) 2009 280 North Inc. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.ProfileDataGridNode = function(profileView, profileNode, owningTree, hasChildren)
{
    this.profileView = profileView;
    this.profileNode = profileNode;

    WebInspector.DataGridNode.call(this, null, hasChildren);

    this.addEventListener("populate", this._populate, this);

    this.tree = owningTree;

    this.childrenByCallUID = {};
    this.lastComparator = null;

    this.callUID = profileNode.callUID;
    this.selfTime = profileNode.selfTime;
    this.totalTime = profileNode.totalTime;
    this.functionName = profileNode.functionName;
    this.numberOfCalls = profileNode.numberOfCalls;
    this.url = profileNode.url;
}

WebInspector.ProfileDataGridNode.prototype = {
    get data()
    {
        function formatMilliseconds(time)
        {
            return Number.secondsToString(time / 1000, WebInspector.UIString.bind(WebInspector), !Preferences.samplingCPUProfiler);
        }

        var data = {};

        data["function"] = this.functionName;
        data["calls"] = this.numberOfCalls;

        if (this.profileView.showSelfTimeAsPercent)
            data["self"] = WebInspector.UIString("%.2f%%", this.selfPercent);
        else
            data["self"] = formatMilliseconds(this.selfTime);

        if (this.profileView.showTotalTimeAsPercent)
            data["total"] = WebInspector.UIString("%.2f%%", this.totalPercent);
        else
            data["total"] = formatMilliseconds(this.totalTime);

        if (this.profileView.showAverageTimeAsPercent)
            data["average"] = WebInspector.UIString("%.2f%%", this.averagePercent);
        else
            data["average"] = formatMilliseconds(this.averageTime);

        return data;
    },

    createCell: function(columnIdentifier)
    {
        var cell = WebInspector.DataGridNode.prototype.createCell.call(this, columnIdentifier);

        if (columnIdentifier === "self" && this._searchMatchedSelfColumn)
            cell.addStyleClass("highlight");
        else if (columnIdentifier === "total" && this._searchMatchedTotalColumn)
            cell.addStyleClass("highlight");
        else if (columnIdentifier === "average" && this._searchMatchedAverageColumn)
            cell.addStyleClass("highlight");
        else if (columnIdentifier === "calls" && this._searchMatchedCallsColumn)
            cell.addStyleClass("highlight");

        if (columnIdentifier !== "function")
            return cell;

        if (this.profileNode._searchMatchedFunctionColumn)
            cell.addStyleClass("highlight");

        if (this.profileNode.url) {
            var fileName = WebInspector.displayNameForURL(this.profileNode.url);

            var urlElement = document.createElement("a");
            urlElement.className = "profile-node-file webkit-html-resource-link";
            urlElement.href = this.profileNode.url;
            urlElement.lineNumber = this.profileNode.lineNumber;

            if (this.profileNode.lineNumber > 0)
                urlElement.textContent = fileName + ":" + this.profileNode.lineNumber;
            else
                urlElement.textContent = fileName;

            cell.insertBefore(urlElement, cell.firstChild);
        }

        return cell;
    },

    select: function(supressSelectedEvent)
    {
        WebInspector.DataGridNode.prototype.select.call(this, supressSelectedEvent);
        this.profileView._dataGridNodeSelected(this);
    },

    deselect: function(supressDeselectedEvent)
    {
        WebInspector.DataGridNode.prototype.deselect.call(this, supressDeselectedEvent);
        this.profileView._dataGridNodeDeselected(this);
    },

    sort: function(/*Function*/ comparator, /*Boolean*/ force)
    {
        var gridNodeGroups = [[this]];

        for (var gridNodeGroupIndex = 0; gridNodeGroupIndex < gridNodeGroups.length; ++gridNodeGroupIndex) {
            var gridNodes = gridNodeGroups[gridNodeGroupIndex];
            var count = gridNodes.length;

            for (var index = 0; index < count; ++index) {
                var gridNode = gridNodes[index];

                // If the grid node is collapsed, then don't sort children (save operation for later).
                // If the grid node has the same sorting as previously, then there is no point in sorting it again.
                if (!force && (!gridNode.expanded || gridNode.lastComparator === comparator)) {
                    if (gridNode.children.length)
                        gridNode.shouldRefreshChildren = true;
                    continue;
                }

                gridNode.lastComparator = comparator;

                var children = gridNode.children;
                var childCount = children.length;

                if (childCount) {
                    children.sort(comparator);

                    for (var childIndex = 0; childIndex < childCount; ++childIndex)
                        children[childIndex]._recalculateSiblings(childIndex);

                    gridNodeGroups.push(children);
                }
            }
        }
    },

    insertChild: function(/*ProfileDataGridNode*/ profileDataGridNode, index)
    {
        WebInspector.DataGridNode.prototype.insertChild.call(this, profileDataGridNode, index);

        this.childrenByCallUID[profileDataGridNode.callUID] = profileDataGridNode;
    },

    removeChild: function(/*ProfileDataGridNode*/ profileDataGridNode)
    {
        WebInspector.DataGridNode.prototype.removeChild.call(this, profileDataGridNode);

        delete this.childrenByCallUID[profileDataGridNode.callUID];
    },

    removeChildren: function(/*ProfileDataGridNode*/ profileDataGridNode)
    {
        WebInspector.DataGridNode.prototype.removeChildren.call(this);

        this.childrenByCallUID = {};
    },

    findChild: function(/*Node*/ node)
    {
        if (!node)
            return null;
        return this.childrenByCallUID[node.callUID];
    },

    get averageTime()
    {
        return this.selfTime / Math.max(1, this.numberOfCalls);
    },

    get averagePercent()
    {
        return this.averageTime / this.tree.totalTime * 100.0;
    },

    get selfPercent()
    {
        return this.selfTime / this.tree.totalTime * 100.0;
    },

    get totalPercent()
    {
        return this.totalTime / this.tree.totalTime * 100.0;
    },

    get _parent()
    {
        return this.parent !== this.dataGrid ? this.parent : this.tree;
    },

    _populate: function(event)
    {
        this._sharedPopulate();

        if (this._parent) {
            var currentComparator = this._parent.lastComparator;

            if (currentComparator)
                this.sort(currentComparator, true);
        }

        if (this.removeEventListener)
            this.removeEventListener("populate", this._populate, this);
    },

    // When focusing and collapsing we modify lots of nodes in the tree.
    // This allows us to restore them all to their original state when we revert.
    _save: function()
    {
        if (this._savedChildren)
            return;

        this._savedSelfTime = this.selfTime;
        this._savedTotalTime = this.totalTime;
        this._savedNumberOfCalls = this.numberOfCalls;

        this._savedChildren = this.children.slice();
    },

    // When focusing and collapsing we modify lots of nodes in the tree.
    // This allows us to restore them all to their original state when we revert.
    _restore: function()
    {
        if (!this._savedChildren)
            return;

        this.selfTime = this._savedSelfTime;
        this.totalTime = this._savedTotalTime;
        this.numberOfCalls = this._savedNumberOfCalls;

        this.removeChildren();

        var children = this._savedChildren;
        var count = children.length;

        for (var index = 0; index < count; ++index) {
            children[index]._restore();
            this.appendChild(children[index]);
        }
    },

    _merge: function(child, shouldAbsorb)
    {
        this.selfTime += child.selfTime;

        if (!shouldAbsorb) {
            this.totalTime += child.totalTime;
            this.numberOfCalls += child.numberOfCalls;
        }

        var children = this.children.slice();

        this.removeChildren();

        var count = children.length;

        for (var index = 0; index < count; ++index) {
            if (!shouldAbsorb || children[index] !== child)
                this.appendChild(children[index]);
        }

        children = child.children.slice();
        count = children.length;

        for (var index = 0; index < count; ++index) {
            var orphanedChild = children[index],
                existingChild = this.childrenByCallUID[orphanedChild.callUID];

            if (existingChild)
                existingChild._merge(orphanedChild, false);
            else
                this.appendChild(orphanedChild);
        }
    }
}

WebInspector.ProfileDataGridNode.prototype.__proto__ = WebInspector.DataGridNode.prototype;

WebInspector.ProfileDataGridTree = function(profileView, profileNode)
{
    this.tree = this;
    this.children = [];

    this.profileView = profileView;

    this.totalTime = profileNode.totalTime;
    this.lastComparator = null;

    this.childrenByCallUID = {};
}

WebInspector.ProfileDataGridTree.prototype = {
    get expanded()
    {
        return true;
    },

    appendChild: function(child)
    {
        this.insertChild(child, this.children.length);
    },

    insertChild: function(child, index)
    {
        this.children.splice(index, 0, child);
        this.childrenByCallUID[child.callUID] = child;
    },

    removeChildren: function()
    {
        this.children = [];
        this.childrenByCallUID = {};
    },

    findChild: WebInspector.ProfileDataGridNode.prototype.findChild,
    sort: WebInspector.ProfileDataGridNode.prototype.sort,

    _save: function()
    {
        if (this._savedChildren)
            return;

        this._savedTotalTime = this.totalTime;
        this._savedChildren = this.children.slice();
    },

    restore: function()
    {
        if (!this._savedChildren)
            return;

        this.children = this._savedChildren;
        this.totalTime = this._savedTotalTime;

        var children = this.children;
        var count = children.length;

        for (var index = 0; index < count; ++index)
            children[index]._restore();

        this._savedChildren = null;
    }
}

WebInspector.ProfileDataGridTree.propertyComparators = [{}, {}];

WebInspector.ProfileDataGridTree.propertyComparator = function(/*String*/ property, /*Boolean*/ isAscending)
{
    var comparator = this.propertyComparators[(isAscending ? 1 : 0)][property];

    if (!comparator) {
        if (isAscending) {
            comparator = function(lhs, rhs)
            {
                if (lhs[property] < rhs[property])
                    return -1;

                if (lhs[property] > rhs[property])
                    return 1;

                return 0;
            }
        } else {
            comparator = function(lhs, rhs)
            {
                if (lhs[property] > rhs[property])
                    return -1;

                if (lhs[property] < rhs[property])
                    return 1;

                return 0;
            }
        }

        this.propertyComparators[(isAscending ? 1 : 0)][property] = comparator;
    }

    return comparator;
}

/* BottomUpProfileDataGridTree.js */

/*
 * Copyright (C) 2009 280 North Inc. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

// Bottom Up Profiling shows the entire callstack backwards:
// The root node is a representation of each individual function called, and each child of that node represents
// a reverse-callstack showing how many of those calls came from it. So, unlike top-down, the statistics in
// each child still represent the root node. We have to be particularly careful of recursion with this mode
// because a root node can represent itself AND an ancestor.

WebInspector.BottomUpProfileDataGridNode = function(/*ProfileView*/ profileView, /*ProfileNode*/ profileNode, /*BottomUpProfileDataGridTree*/ owningTree)
{
    // In bottom up mode, our parents are our children since we display an inverted tree.
    // However, we don't want to show the very top parent since it is redundant.
    var hasChildren = !!(profileNode.parent && profileNode.parent.parent);

    WebInspector.ProfileDataGridNode.call(this, profileView, profileNode, owningTree, hasChildren);

    this._remainingNodeInfos = [];
}

WebInspector.BottomUpProfileDataGridNode.prototype = {
    _takePropertiesFromProfileDataGridNode: function(/*ProfileDataGridNode*/ profileDataGridNode)
    {
        this._save();

        this.selfTime = profileDataGridNode.selfTime;
        this.totalTime = profileDataGridNode.totalTime;
        this.numberOfCalls = profileDataGridNode.numberOfCalls;
    },

    // When focusing, we keep just the members of the callstack.
    _keepOnlyChild: function(/*ProfileDataGridNode*/ child)
    {
        this._save();

        this.removeChildren();
        this.appendChild(child);
    },

    _exclude: function(aCallUID)
    {
        if (this._remainingNodeInfos)
            this._populate();

        this._save();

        var children = this.children;
        var index = this.children.length;

        while (index--)
            children[index]._exclude(aCallUID);

        var child = this.childrenByCallUID[aCallUID];

        if (child)
            this._merge(child, true);
    },

    _merge: function(/*ProfileDataGridNode*/ child, /*Boolean*/ shouldAbsorb)
    {
        this.selfTime -= child.selfTime;

        WebInspector.ProfileDataGridNode.prototype._merge.call(this, child, shouldAbsorb);
    },

    _sharedPopulate: function()
    {
        var remainingNodeInfos = this._remainingNodeInfos;
        var count = remainingNodeInfos.length;

        for (var index = 0; index < count; ++index) {
            var nodeInfo = remainingNodeInfos[index];
            var ancestor = nodeInfo.ancestor;
            var focusNode = nodeInfo.focusNode;
            var child = this.findChild(ancestor);

            // If we already have this child, then merge the data together.
            if (child) {
                var totalTimeAccountedFor = nodeInfo.totalTimeAccountedFor;

                child.selfTime += focusNode.selfTime;
                child.numberOfCalls += focusNode.numberOfCalls;

                if (!totalTimeAccountedFor)
                    child.totalTime += focusNode.totalTime;
            } else {
                // If not, add it as a true ancestor.
                // In heavy mode, we take our visual identity from ancestor node...
                var child = new WebInspector.BottomUpProfileDataGridNode(this.profileView, ancestor, this.tree);

                if (ancestor !== focusNode) {
                    // but the actual statistics from the "root" node (bottom of the callstack).
                    child.selfTime = focusNode.selfTime;
                    child.totalTime = focusNode.totalTime;
                    child.numberOfCalls = focusNode.numberOfCalls;
                }

                this.appendChild(child);
            }

            var parent = ancestor.parent;
            if (parent && parent.parent) {
                nodeInfo.ancestor = parent;
                child._remainingNodeInfos.push(nodeInfo);
            }
        }

        delete this._remainingNodeInfos;
    }
}

WebInspector.BottomUpProfileDataGridNode.prototype.__proto__ = WebInspector.ProfileDataGridNode.prototype;

WebInspector.BottomUpProfileDataGridTree = function(/*ProfileView*/ aProfileView, /*ProfileNode*/ aProfileNode)
{
    WebInspector.ProfileDataGridTree.call(this, aProfileView, aProfileNode);

    // Iterate each node in pre-order.
    var profileNodeUIDs = 0;
    var profileNodeGroups = [[], [aProfileNode]];
    var visitedProfileNodesForCallUID = {};

    this._remainingNodeInfos = [];

    for (var profileNodeGroupIndex = 0; profileNodeGroupIndex < profileNodeGroups.length; ++profileNodeGroupIndex) {
        var parentProfileNodes = profileNodeGroups[profileNodeGroupIndex];
        var profileNodes = profileNodeGroups[++profileNodeGroupIndex];
        var count = profileNodes.length;

        for (var index = 0; index < count; ++index) {
            var profileNode = profileNodes[index];

            if (!profileNode.UID)
                profileNode.UID = ++profileNodeUIDs;

            if (profileNode.head && profileNode !== profileNode.head) {
                // The total time of this ancestor is accounted for if we're in any form of recursive cycle.
                var visitedNodes = visitedProfileNodesForCallUID[profileNode.callUID];
                var totalTimeAccountedFor = false;

                if (!visitedNodes) {
                    visitedNodes = {}
                    visitedProfileNodesForCallUID[profileNode.callUID] = visitedNodes;
                } else {
                    // The total time for this node has already been accounted for iff one of it's parents has already been visited.
                    // We can do this check in this style because we are traversing the tree in pre-order.
                    var parentCount = parentProfileNodes.length;
                    for (var parentIndex = 0; parentIndex < parentCount; ++parentIndex) {
                        if (visitedNodes[parentProfileNodes[parentIndex].UID]) {
                            totalTimeAccountedFor = true;
                            break;
                        }
                    }
                }

                visitedNodes[profileNode.UID] = true;

                this._remainingNodeInfos.push({ ancestor:profileNode, focusNode:profileNode, totalTimeAccountedFor:totalTimeAccountedFor });
            }

            var children = profileNode.children;
            if (children.length) {
                profileNodeGroups.push(parentProfileNodes.concat([profileNode]))
                profileNodeGroups.push(children);
            }
        }
    }

    // Populate the top level nodes.
    WebInspector.BottomUpProfileDataGridNode.prototype._populate.call(this);

    return this;
}

WebInspector.BottomUpProfileDataGridTree.prototype = {
    // When focusing, we keep the entire callstack up to this ancestor.
    focus: function(/*ProfileDataGridNode*/ profileDataGridNode)
    {
        if (!profileDataGridNode)
            return;

        this._save();

        var currentNode = profileDataGridNode;
        var focusNode = profileDataGridNode;

        while (currentNode.parent && (currentNode instanceof WebInspector.ProfileDataGridNode)) {
            currentNode._takePropertiesFromProfileDataGridNode(profileDataGridNode);

            focusNode = currentNode;
            currentNode = currentNode.parent;

            if (currentNode instanceof WebInspector.ProfileDataGridNode)
                currentNode._keepOnlyChild(focusNode);
        }

        this.children = [focusNode];
        this.totalTime = profileDataGridNode.totalTime;
    },

    exclude: function(/*ProfileDataGridNode*/ profileDataGridNode)
    {
        if (!profileDataGridNode)
            return;

        this._save();

        var excludedCallUID = profileDataGridNode.callUID;
        var excludedTopLevelChild = this.childrenByCallUID[excludedCallUID];

        // If we have a top level node that is excluded, get rid of it completely (not keeping children),
        // since bottom up data relies entirely on the root node.
        if (excludedTopLevelChild)
            this.children.remove(excludedTopLevelChild);

        var children = this.children;
        var count = children.length;

        for (var index = 0; index < count; ++index)
            children[index]._exclude(excludedCallUID);

        if (this.lastComparator)
            this.sort(this.lastComparator, true);
    },

    _sharedPopulate: WebInspector.BottomUpProfileDataGridNode.prototype._sharedPopulate
}

WebInspector.BottomUpProfileDataGridTree.prototype.__proto__ = WebInspector.ProfileDataGridTree.prototype;


/* TopDownProfileDataGridTree.js */

/*
 * Copyright (C) 2009 280 North Inc. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.TopDownProfileDataGridNode = function(/*ProfileView*/ profileView, /*ProfileNode*/ profileNode, /*TopDownProfileDataGridTree*/ owningTree)
{
    var hasChildren = (profileNode.children && profileNode.children.length);

    WebInspector.ProfileDataGridNode.call(this, profileView, profileNode, owningTree, hasChildren);

    this._remainingChildren = profileNode.children;
}

WebInspector.TopDownProfileDataGridNode.prototype = {
    _sharedPopulate: function()
    {
        var children = this._remainingChildren;
        var childrenLength = children.length;

        for (var i = 0; i < childrenLength; ++i)
            this.appendChild(new WebInspector.TopDownProfileDataGridNode(this.profileView, children[i], this.tree));

        this._remainingChildren = null;
    },

    _exclude: function(aCallUID)
    {
        if (this._remainingChildren)
            this._populate();

        this._save();

        var children = this.children;
        var index = this.children.length;

        while (index--)
            children[index]._exclude(aCallUID);

        var child = this.childrenByCallUID[aCallUID];

        if (child)
            this._merge(child, true);
    }
}

WebInspector.TopDownProfileDataGridNode.prototype.__proto__ = WebInspector.ProfileDataGridNode.prototype;

WebInspector.TopDownProfileDataGridTree = function(/*ProfileView*/ profileView, /*ProfileNode*/ profileNode)
{
    WebInspector.ProfileDataGridTree.call(this, profileView, profileNode);

    this._remainingChildren = profileNode.children;

    WebInspector.TopDownProfileDataGridNode.prototype._populate.call(this);
}

WebInspector.TopDownProfileDataGridTree.prototype = {
    focus: function(/*ProfileDataGridNode*/ profileDataGrideNode)
    {
        if (!profileDataGrideNode)
            return;

        this._save();

        this.children = [profileDataGrideNode];
        this.totalTime = profileDataGrideNode.totalTime;
    },

    exclude: function(/*ProfileDataGridNode*/ profileDataGrideNode)
    {
        if (!profileDataGrideNode)
            return;

        this._save();

        var excludedCallUID = profileDataGrideNode.callUID;

        WebInspector.TopDownProfileDataGridNode.prototype._exclude.call(this, excludedCallUID);

        if (this.lastComparator)
            this.sort(this.lastComparator, true);
    },

    _merge: WebInspector.TopDownProfileDataGridNode.prototype._merge,

    _sharedPopulate: WebInspector.TopDownProfileDataGridNode.prototype._sharedPopulate
}

WebInspector.TopDownProfileDataGridTree.prototype.__proto__ = WebInspector.ProfileDataGridTree.prototype;

/* ProfileView.js */

/*
 * Copyright (C) 2008 Apple Inc. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

// FIXME: Rename the file.

WebInspector.CPUProfileView = function(profile)
{
    WebInspector.View.call(this);

    this.element.addStyleClass("profile-view");

    this.showSelfTimeAsPercent = true;
    this.showTotalTimeAsPercent = true;
    this.showAverageTimeAsPercent = true;

    var columns = { "self": { title: WebInspector.UIString("Self"), width: "72px", sort: "descending", sortable: true },
                    "total": { title: WebInspector.UIString("Total"), width: "72px", sortable: true },
                    "average": { title: WebInspector.UIString("Average"), width: "72px", sortable: true },
                    "calls": { title: WebInspector.UIString("Calls"), width: "54px", sortable: true },
                    "function": { title: WebInspector.UIString("Function"), disclosure: true, sortable: true } };

    if (Preferences.samplingCPUProfiler) {
        delete columns.average;
        delete columns.calls;
    }

    this.dataGrid = new WebInspector.DataGrid(columns);
    this.dataGrid.addEventListener("sorting changed", this._sortData, this);
    this.dataGrid.element.addEventListener("mousedown", this._mouseDownInDataGrid.bind(this), true);
    this.element.appendChild(this.dataGrid.element);

    this.viewSelectElement = document.createElement("select");
    this.viewSelectElement.className = "status-bar-item";
    this.viewSelectElement.addEventListener("change", this._changeView.bind(this), false);
    this.view = "Heavy";

    var heavyViewOption = document.createElement("option");
    heavyViewOption.label = WebInspector.UIString("Heavy (Bottom Up)");
    var treeViewOption = document.createElement("option");
    treeViewOption.label = WebInspector.UIString("Tree (Top Down)");
    this.viewSelectElement.appendChild(heavyViewOption);
    this.viewSelectElement.appendChild(treeViewOption);

    this.percentButton = new WebInspector.StatusBarButton("", "percent-time-status-bar-item");
    this.percentButton.addEventListener("click", this._percentClicked.bind(this), false);

    this.focusButton = new WebInspector.StatusBarButton(WebInspector.UIString("Focus selected function."), "focus-profile-node-status-bar-item");
    this.focusButton.disabled = true;
    this.focusButton.addEventListener("click", this._focusClicked.bind(this), false);

    this.excludeButton = new WebInspector.StatusBarButton(WebInspector.UIString("Exclude selected function."), "exclude-profile-node-status-bar-item");
    this.excludeButton.disabled = true;
    this.excludeButton.addEventListener("click", this._excludeClicked.bind(this), false);

    this.resetButton = new WebInspector.StatusBarButton(WebInspector.UIString("Restore all functions."), "reset-profile-status-bar-item");
    this.resetButton.visible = false;
    this.resetButton.addEventListener("click", this._resetClicked.bind(this), false);

    this.profile = profile;

    var self = this;
    function profileCallback(profile)
    {
        self.profile = profile;
        self._assignParentsInProfile();
      
        self.profileDataGridTree = self.bottomUpProfileDataGridTree;
        self.profileDataGridTree.sort(WebInspector.ProfileDataGridTree.propertyComparator("selfTime", false));
     
        self.refresh();
     
        self._updatePercentButton();
    }

    var callId = WebInspector.Callback.wrap(profileCallback);
    InspectorController.getProfile(callId, this.profile.uid);
}

WebInspector.CPUProfileView.prototype = {
    get statusBarItems()
    {
        return [this.viewSelectElement, this.percentButton.element, this.focusButton.element, this.excludeButton.element, this.resetButton.element];
    },

    get profile()
    {
        return this._profile;
    },

    set profile(profile)
    {
        this._profile = profile;
    },

    get bottomUpProfileDataGridTree()
    {
        if (!this._bottomUpProfileDataGridTree)
            this._bottomUpProfileDataGridTree = new WebInspector.BottomUpProfileDataGridTree(this, this.profile.head);
        return this._bottomUpProfileDataGridTree;
    },

    get topDownProfileDataGridTree()
    {
        if (!this._topDownProfileDataGridTree)
            this._topDownProfileDataGridTree = new WebInspector.TopDownProfileDataGridTree(this, this.profile.head);
        return this._topDownProfileDataGridTree;
    },

    get currentTree()
    {
        return this._currentTree;
    },

    set currentTree(tree)
    {
        this._currentTree = tree;
        this.refresh();
    },

    get topDownTree()
    {
        if (!this._topDownTree) {
            this._topDownTree = WebInspector.TopDownTreeFactory.create(this.profile.head);
            this._sortProfile(this._topDownTree);
        }

        return this._topDownTree;
    },

    get bottomUpTree()
    {
        if (!this._bottomUpTree) {
            this._bottomUpTree = WebInspector.BottomUpTreeFactory.create(this.profile.head);
            this._sortProfile(this._bottomUpTree);
        }

        return this._bottomUpTree;
    },

    show: function(parentElement)
    {
        WebInspector.View.prototype.show.call(this, parentElement);
        this.dataGrid.updateWidths();
    },

    hide: function()
    {
        WebInspector.View.prototype.hide.call(this);
        this._currentSearchResultIndex = -1;
    },

    resize: function()
    {
        if (this.dataGrid)
            this.dataGrid.updateWidths();
    },

    refresh: function()
    {
        var selectedProfileNode = this.dataGrid.selectedNode ? this.dataGrid.selectedNode.profileNode : null;

        this.dataGrid.removeChildren();

        var children = this.profileDataGridTree.children;
        var count = children.length;

        for (var index = 0; index < count; ++index)
            this.dataGrid.appendChild(children[index]);

        if (selectedProfileNode)
            selectedProfileNode.selected = true;
    },

    refreshVisibleData: function()
    {
        var child = this.dataGrid.children[0];
        while (child) {
            child.refresh();
            child = child.traverseNextNode(false, null, true);
        }
    },

    refreshShowAsPercents: function()
    {
        this._updatePercentButton();
        this.refreshVisibleData();
    },

    searchCanceled: function()
    {
        if (this._searchResults) {
            for (var i = 0; i < this._searchResults.length; ++i) {
                var profileNode = this._searchResults[i].profileNode;

                delete profileNode._searchMatchedSelfColumn;
                delete profileNode._searchMatchedTotalColumn;
                delete profileNode._searchMatchedCallsColumn;
                delete profileNode._searchMatchedFunctionColumn;

                profileNode.refresh();
            }
        }

        delete this._searchFinishedCallback;
        this._currentSearchResultIndex = -1;
        this._searchResults = [];
    },

    performSearch: function(query, finishedCallback)
    {
        // Call searchCanceled since it will reset everything we need before doing a new search.
        this.searchCanceled();

        query = query.trimWhitespace();

        if (!query.length)
            return;

        this._searchFinishedCallback = finishedCallback;

        var greaterThan = (query.indexOf(">") === 0);
        var lessThan = (query.indexOf("<") === 0);
        var equalTo = (query.indexOf("=") === 0 || ((greaterThan || lessThan) && query.indexOf("=") === 1));
        var percentUnits = (query.lastIndexOf("%") === (query.length - 1));
        var millisecondsUnits = (query.length > 2 && query.lastIndexOf("ms") === (query.length - 2));
        var secondsUnits = (!millisecondsUnits && query.lastIndexOf("s") === (query.length - 1));

        var queryNumber = parseFloat(query);
        if (greaterThan || lessThan || equalTo) {
            if (equalTo && (greaterThan || lessThan))
                queryNumber = parseFloat(query.substring(2));
            else
                queryNumber = parseFloat(query.substring(1));
        }

        var queryNumberMilliseconds = (secondsUnits ? (queryNumber * 1000) : queryNumber);

        // Make equalTo implicitly true if it wasn't specified there is no other operator.
        if (!isNaN(queryNumber) && !(greaterThan || lessThan))
            equalTo = true;

        function matchesQuery(/*ProfileDataGridNode*/ profileDataGridNode)
        {
            delete profileDataGridNode._searchMatchedSelfColumn;
            delete profileDataGridNode._searchMatchedTotalColumn;
            delete profileDataGridNode._searchMatchedAverageColumn;
            delete profileDataGridNode._searchMatchedCallsColumn;
            delete profileDataGridNode._searchMatchedFunctionColumn;

            if (percentUnits) {
                if (lessThan) {
                    if (profileDataGridNode.selfPercent < queryNumber)
                        profileDataGridNode._searchMatchedSelfColumn = true;
                    if (profileDataGridNode.totalPercent < queryNumber)
                        profileDataGridNode._searchMatchedTotalColumn = true;
                    if (profileDataGridNode.averagePercent < queryNumberMilliseconds)
                        profileDataGridNode._searchMatchedAverageColumn = true;
                } else if (greaterThan) {
                    if (profileDataGridNode.selfPercent > queryNumber)
                        profileDataGridNode._searchMatchedSelfColumn = true;
                    if (profileDataGridNode.totalPercent > queryNumber)
                        profileDataGridNode._searchMatchedTotalColumn = true;
                    if (profileDataGridNode.averagePercent < queryNumberMilliseconds)
                        profileDataGridNode._searchMatchedAverageColumn = true;
                }

                if (equalTo) {
                    if (profileDataGridNode.selfPercent == queryNumber)
                        profileDataGridNode._searchMatchedSelfColumn = true;
                    if (profileDataGridNode.totalPercent == queryNumber)
                        profileDataGridNode._searchMatchedTotalColumn = true;
                    if (profileDataGridNode.averagePercent < queryNumberMilliseconds)
                        profileDataGridNode._searchMatchedAverageColumn = true;
                }
            } else if (millisecondsUnits || secondsUnits) {
                if (lessThan) {
                    if (profileDataGridNode.selfTime < queryNumberMilliseconds)
                        profileDataGridNode._searchMatchedSelfColumn = true;
                    if (profileDataGridNode.totalTime < queryNumberMilliseconds)
                        profileDataGridNode._searchMatchedTotalColumn = true;
                    if (profileDataGridNode.averageTime < queryNumberMilliseconds)
                        profileDataGridNode._searchMatchedAverageColumn = true;
                } else if (greaterThan) {
                    if (profileDataGridNode.selfTime > queryNumberMilliseconds)
                        profileDataGridNode._searchMatchedSelfColumn = true;
                    if (profileDataGridNode.totalTime > queryNumberMilliseconds)
                        profileDataGridNode._searchMatchedTotalColumn = true;
                    if (profileDataGridNode.averageTime > queryNumberMilliseconds)
                        profileDataGridNode._searchMatchedAverageColumn = true;
                }

                if (equalTo) {
                    if (profileDataGridNode.selfTime == queryNumberMilliseconds)
                        profileDataGridNode._searchMatchedSelfColumn = true;
                    if (profileDataGridNode.totalTime == queryNumberMilliseconds)
                        profileDataGridNode._searchMatchedTotalColumn = true;
                    if (profileDataGridNode.averageTime == queryNumberMilliseconds)
                        profileDataGridNode._searchMatchedAverageColumn = true;
                }
            } else {
                if (equalTo && profileDataGridNode.numberOfCalls == queryNumber)
                    profileDataGridNode._searchMatchedCallsColumn = true;
                if (greaterThan && profileDataGridNode.numberOfCalls > queryNumber)
                    profileDataGridNode._searchMatchedCallsColumn = true;
                if (lessThan && profileDataGridNode.numberOfCalls < queryNumber)
                    profileDataGridNode._searchMatchedCallsColumn = true;
            }

            if (profileDataGridNode.functionName.hasSubstring(query, true) || profileDataGridNode.url.hasSubstring(query, true))
                profileDataGridNode._searchMatchedFunctionColumn = true;

            if (profileDataGridNode._searchMatchedSelfColumn ||
                profileDataGridNode._searchMatchedTotalColumn ||
                profileDataGridNode._searchMatchedAverageColumn ||
                profileDataGridNode._searchMatchedCallsColumn ||
                profileDataGridNode._searchMatchedFunctionColumn)
            {
                profileDataGridNode.refresh();
                return true;
            }

            return false;
        }

        var current = this.profileDataGridTree.children[0];

        while (current) {
            if (matchesQuery(current)) {
                this._searchResults.push({ profileNode: current });
            }

            current = current.traverseNextNode(false, null, false);
        }

        finishedCallback(this, this._searchResults.length);
    },

    jumpToFirstSearchResult: function()
    {
        if (!this._searchResults || !this._searchResults.length)
            return;
        this._currentSearchResultIndex = 0;
        this._jumpToSearchResult(this._currentSearchResultIndex);
    },

    jumpToLastSearchResult: function()
    {
        if (!this._searchResults || !this._searchResults.length)
            return;
        this._currentSearchResultIndex = (this._searchResults.length - 1);
        this._jumpToSearchResult(this._currentSearchResultIndex);
    },

    jumpToNextSearchResult: function()
    {
        if (!this._searchResults || !this._searchResults.length)
            return;
        if (++this._currentSearchResultIndex >= this._searchResults.length)
            this._currentSearchResultIndex = 0;
        this._jumpToSearchResult(this._currentSearchResultIndex);
    },

    jumpToPreviousSearchResult: function()
    {
        if (!this._searchResults || !this._searchResults.length)
            return;
        if (--this._currentSearchResultIndex < 0)
            this._currentSearchResultIndex = (this._searchResults.length - 1);
        this._jumpToSearchResult(this._currentSearchResultIndex);
    },

    showingFirstSearchResult: function()
    {
        return (this._currentSearchResultIndex === 0);
    },

    showingLastSearchResult: function()
    {
        return (this._searchResults && this._currentSearchResultIndex === (this._searchResults.length - 1));
    },

    _jumpToSearchResult: function(index)
    {
        var searchResult = this._searchResults[index];
        if (!searchResult)
            return;

        var profileNode = searchResult.profileNode;
        profileNode.reveal();
        profileNode.select();
    },

    _changeView: function(event)
    {
        if (!event || !this.profile)
            return;

        if (event.target.selectedIndex == 1 && this.view == "Heavy") {
            this.profileDataGridTree = this.topDownProfileDataGridTree;
            this._sortProfile();
            this.view = "Tree";
        } else if (event.target.selectedIndex == 0 && this.view == "Tree") {
            this.profileDataGridTree = this.bottomUpProfileDataGridTree;
            this._sortProfile();
            this.view = "Heavy";
        }

        if (!this.currentQuery || !this._searchFinishedCallback || !this._searchResults)
            return;

        // The current search needs to be performed again. First negate out previous match
        // count by calling the search finished callback with a negative number of matches.
        // Then perform the search again the with same query and callback.
        this._searchFinishedCallback(this, -this._searchResults.length);
        this.performSearch(this.currentQuery, this._searchFinishedCallback);
    },

    _percentClicked: function(event)
    {
        var currentState = this.showSelfTimeAsPercent && this.showTotalTimeAsPercent && this.showAverageTimeAsPercent;
        this.showSelfTimeAsPercent = !currentState;
        this.showTotalTimeAsPercent = !currentState;
        this.showAverageTimeAsPercent = !currentState;
        this.refreshShowAsPercents();
    },

    _updatePercentButton: function()
    {
        if (this.showSelfTimeAsPercent && this.showTotalTimeAsPercent && this.showAverageTimeAsPercent) {
            this.percentButton.title = WebInspector.UIString("Show absolute total and self times.");
            this.percentButton.toggled = true;
        } else {
            this.percentButton.title = WebInspector.UIString("Show total and self times as percentages.");
            this.percentButton.toggled = false;
        }
    },

    _focusClicked: function(event)
    {
        if (!this.dataGrid.selectedNode)
            return;

        this.resetButton.visible = true;
        this.profileDataGridTree.focus(this.dataGrid.selectedNode);
        this.refresh();
        this.refreshVisibleData();
    },

    _excludeClicked: function(event)
    {
        var selectedNode = this.dataGrid.selectedNode

        if (!selectedNode)
            return;

        selectedNode.deselect();

        this.resetButton.visible = true;
        this.profileDataGridTree.exclude(selectedNode);
        this.refresh();
        this.refreshVisibleData();
    },

    _resetClicked: function(event)
    {
        this.resetButton.visible = false;
        this.profileDataGridTree.restore();
        this.refresh();
        this.refreshVisibleData();
    },

    _dataGridNodeSelected: function(node)
    {
        this.focusButton.disabled = false;
        this.excludeButton.disabled = false;
    },

    _dataGridNodeDeselected: function(node)
    {
        this.focusButton.disabled = true;
        this.excludeButton.disabled = true;
    },

    _sortData: function(event)
    {
        this._sortProfile(this.profile);
    },

    _sortProfile: function()
    {
        var sortAscending = this.dataGrid.sortOrder === "ascending";
        var sortColumnIdentifier = this.dataGrid.sortColumnIdentifier;
        var sortProperty = {
                "average": "averageTime",
                "self": "selfTime",
                "total": "totalTime",
                "calls": "numberOfCalls",
                "function": "functionName"
            }[sortColumnIdentifier];

        this.profileDataGridTree.sort(WebInspector.ProfileDataGridTree.propertyComparator(sortProperty, sortAscending));

        this.refresh();
    },

    _mouseDownInDataGrid: function(event)
    {
        if (event.detail < 2)
            return;

        var cell = event.target.enclosingNodeOrSelfWithNodeName("td");
        if (!cell || (!cell.hasStyleClass("total-column") && !cell.hasStyleClass("self-column") && !cell.hasStyleClass("average-column")))
            return;

        if (cell.hasStyleClass("total-column"))
            this.showTotalTimeAsPercent = !this.showTotalTimeAsPercent;
        else if (cell.hasStyleClass("self-column"))
            this.showSelfTimeAsPercent = !this.showSelfTimeAsPercent;
        else if (cell.hasStyleClass("average-column"))
            this.showAverageTimeAsPercent = !this.showAverageTimeAsPercent;

        this.refreshShowAsPercents();

        event.preventDefault();
        event.stopPropagation();
    },

    _assignParentsInProfile: function()
    {
        var head = this.profile.head;
        head.parent = null;
        head.head = null;
        var nodesToTraverse = [ { parent: head, children: head.children } ];
        while (nodesToTraverse.length > 0) {
            var pair = nodesToTraverse.shift();
            var parent = pair.parent;
            var children = pair.children;
            var length = children.length;
            for (var i = 0; i < length; ++i) {
                children[i].head = head;
                children[i].parent = parent;
                if (children[i].children.length > 0)
                    nodesToTraverse.push({ parent: children[i], children: children[i].children });
            }
        }
    }
}

WebInspector.CPUProfileView.prototype.__proto__ = WebInspector.View.prototype;

WebInspector.CPUProfileType = function()
{
    WebInspector.ProfileType.call(this, WebInspector.CPUProfileType.TypeId, WebInspector.UIString("CPU PROFILES"));
    this._recording = false;
}

WebInspector.CPUProfileType.TypeId = "CPU";

WebInspector.CPUProfileType.prototype = {
    get buttonTooltip()
    {
        return this._recording ? WebInspector.UIString("Stop profiling.") : WebInspector.UIString("Start profiling.");
    },

    get buttonStyle()
    {
        return this._recording ? "record-profile-status-bar-item status-bar-item toggled-on" : "record-profile-status-bar-item status-bar-item";
    },

    buttonClicked: function()
    {
        this._recording = !this._recording;

        if (this._recording)
            InspectorController.startProfiling();
        else
            InspectorController.stopProfiling();
    },

    setRecordingProfile: function(isProfiling)
    {
        this._recording = isProfiling;
    },

    createSidebarTreeElementForProfile: function(profile)
    {
        return new WebInspector.ProfileSidebarTreeElement(profile);
    },

    createView: function(profile)
    {
        return new WebInspector.CPUProfileView(profile);
    }
}

WebInspector.CPUProfileType.prototype.__proto__ = WebInspector.ProfileType.prototype;

/* DOMAgent.js */

/*
 * Copyright (C) 2009 Google Inc. All rights reserved.
 * Copyright (C) 2009 Joseph Pecoraro
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.DOMNode = function(doc, payload) {
    this.ownerDocument = doc;

    this.id = payload.id;
    this.nodeType = payload.nodeType;
    this.nodeName = payload.nodeName;
    this.localName = payload.localName;
    this._nodeValue = payload.nodeValue;
    this.textContent = this.nodeValue;

    this.attributes = [];
    this._attributesMap = {};
    if (payload.attributes)
        this._setAttributesPayload(payload.attributes);

    this._childNodeCount = payload.childNodeCount;
    this.children = null;

    this.nextSibling = null;
    this.prevSibling = null;
    this.firstChild = null;
    this.lastChild = null;
    this.parentNode = null;

    if (payload.children)
        this._setChildrenPayload(payload.children);

    this._computedStyle = null;
    this.style = null;
    this._matchedCSSRules = [];

    if (this.nodeType == Node.ELEMENT_NODE) {
        // HTML and BODY from internal iframes should not overwrite top-level ones.
        if (!this.ownerDocument.documentElement && this.nodeName === "HTML")
            this.ownerDocument.documentElement = this;
        if (!this.ownerDocument.body && this.nodeName === "BODY")
            this.ownerDocument.body = this;
    }
}

WebInspector.DOMNode.prototype = {
    hasAttributes: function()
    {
        return this.attributes.length > 0;
    },

    hasChildNodes: function()  {
        return this._childNodeCount > 0;
    },

    get nodeValue() {
        return this._nodeValue;
    },

    set nodeValue(value) {
        if (this.nodeType != Node.TEXT_NODE)
            return;
        var self = this;
        var callback = function()
        {
            self._nodeValue = value;
            self.textContent = value;
        };
        this.ownerDocument._domAgent.setTextNodeValueAsync(this, value, callback);
    },

    getAttribute: function(name)
    {
        var attr = this._attributesMap[name];
        return attr ? attr.value : undefined;
    },

    setAttribute: function(name, value)
    {
        var self = this;
        var callback = function()
        {
            var attr = self._attributesMap[name];
            if (attr)
                attr.value = value;
            else
                attr = self._addAttribute(name, value);
        };
        this.ownerDocument._domAgent.setAttributeAsync(this, name, value, callback);
    },

    removeAttribute: function(name)
    {
        var self = this;
        var callback = function()
        {
            delete self._attributesMap[name];
            for (var i = 0;  i < self.attributes.length; ++i) {
                if (self.attributes[i].name == name) {
                    self.attributes.splice(i, 1);
                    break;
                }
            }
        };
        this.ownerDocument._domAgent.removeAttributeAsync(this, name, callback);
    },

    _setAttributesPayload: function(attrs)
    {
        for (var i = 0; i < attrs.length; i += 2)
            this._addAttribute(attrs[i], attrs[i + 1]);
    },

    _insertChild: function(prev, payload)
    {
        var node = new WebInspector.DOMNode(this.ownerDocument, payload);
        if (!prev)
            // First node
            this.children = [ node ];
        else
            this.children.splice(this.children.indexOf(prev) + 1, 0, node);
        this._renumber();
        return node;
    },

    removeChild_: function(node)
    {
        this.children.splice(this.children.indexOf(node), 1);
        node.parentNode = null;
        this._renumber();
    },

    _setChildrenPayload: function(payloads)
    {
        this.children = [];
        for (var i = 0; i < payloads.length; ++i) {
            var payload = payloads[i];
            var node = new WebInspector.DOMNode(this.ownerDocument, payload);
            this.children.push(node);
        }
        this._renumber();
    },

    _renumber: function()
    {
        this._childNodeCount = this.children.length;
        if (this._childNodeCount == 0) {
            this.firstChild = null;
            this.lastChild = null;
            return;
        }
        this.firstChild = this.children[0];
        this.lastChild = this.children[this._childNodeCount - 1];
        for (var i = 0; i < this._childNodeCount; ++i) {
            var child = this.children[i];
            child.nextSibling = i + 1 < this._childNodeCount ? this.children[i + 1] : null;
            child.prevSibling = i - 1 >= 0 ? this.children[i - 1] : null;
            child.parentNode = this;
        }
    },

    _addAttribute: function(name, value)
    {
        var attr = {
            "name": name,
            "value": value,
            "_node": this
        };
        this._attributesMap[name] = attr;
        this.attributes.push(attr);
    },

    _setStyles: function(computedStyle, inlineStyle, styleAttributes, matchedCSSRules)
    {
        this._computedStyle = new WebInspector.CSSStyleDeclaration(computedStyle);
        this.style = new WebInspector.CSSStyleDeclaration(inlineStyle);

        for (var name in styleAttributes) {
            if (this._attributesMap[name])
                this._attributesMap[name].style = new WebInspector.CSSStyleDeclaration(styleAttributes[name]);
        }

        this._matchedCSSRules = [];
        for (var i = 0; i < matchedCSSRules.length; i++)
            this._matchedCSSRules.push(WebInspector.CSSStyleDeclaration.parseRule(matchedCSSRules[i]));
    },

    _clearStyles: function()
    {
        this.computedStyle = null;
        this.style = null;
        for (var name in this._attributesMap)
            this._attributesMap[name].style = null;
        this._matchedCSSRules = null;
    }
}

WebInspector.DOMDocument = function(domAgent, defaultView, payload)
{
    WebInspector.DOMNode.call(this, this, payload);
    this._listeners = {};
    this._domAgent = domAgent;
    this.defaultView = defaultView;
}

WebInspector.DOMDocument.prototype = {

    addEventListener: function(name, callback)
    {
        var listeners = this._listeners[name];
        if (!listeners) {
            listeners = [];
            this._listeners[name] = listeners;
        }
        listeners.push(callback);
    },

    removeEventListener: function(name, callback)
    {
        var listeners = this._listeners[name];
        if (!listeners)
            return;

        var index = listeners.indexOf(callback);
        if (index != -1)
            listeners.splice(index, 1);
    },

    _fireDomEvent: function(name, event)
    {
        var listeners = this._listeners[name];
        if (!listeners)
            return;

        for (var i = 0; i < listeners.length; ++i) {
            var listener = listeners[i];
            listener.call(this, event);
        }
    }
}

WebInspector.DOMDocument.prototype.__proto__ = WebInspector.DOMNode.prototype;


WebInspector.DOMWindow = function(domAgent)
{
    this._domAgent = domAgent;
}

WebInspector.DOMWindow.prototype = {
    get document()
    {
        return this._domAgent.document;
    },

    get Node()
    {
        return WebInspector.DOMNode;
    },

    get Element()
    {
        return WebInspector.DOMNode;
    },

    Object: function()
    {
    },

    getComputedStyle: function(node)
    {
        return node._computedStyle;
    },

    getMatchedCSSRules: function(node, pseudoElement, authorOnly)
    {
        return node._matchedCSSRules;
    }
}

WebInspector.DOMAgent = function() {
    this._window = new WebInspector.DOMWindow(this);
    this._idToDOMNode = null;
    this.document = null;
}

WebInspector.DOMAgent.prototype = {
    get domWindow()
    {
        return this._window;
    },

    getChildNodesAsync: function(parent, callback)
    {
        var children = parent.children;
        if (children) {
            callback(children);
            return;
        }
        function mycallback() {
            callback(parent.children);
        }
        var callId = WebInspector.Callback.wrap(mycallback);
        InspectorController.getChildNodes(callId, parent.id);
    },

    setAttributeAsync: function(node, name, value, callback)
    {
        var mycallback = this._didApplyDomChange.bind(this, node, callback);
        InspectorController.setAttribute(WebInspector.Callback.wrap(mycallback), node.id, name, value);
    },

    removeAttributeAsync: function(node, name, callback)
    {
        var mycallback = this._didApplyDomChange.bind(this, node, callback);
        InspectorController.removeAttribute(WebInspector.Callback.wrap(mycallback), node.id, name);
    },

    setTextNodeValueAsync: function(node, text, callback)
    {
        var mycallback = this._didApplyDomChange.bind(this, node, callback);
        InspectorController.setTextNodeValue(WebInspector.Callback.wrap(mycallback), node.id, text);
    },

    _didApplyDomChange: function(node, callback, success)
    {
        if (!success)
            return;
        callback();
        // TODO(pfeldman): Fix this hack.
        var elem = WebInspector.panels.elements.treeOutline.findTreeElement(node);
        if (elem) {
            elem._updateTitle();
        }
    },

    _attributesUpdated: function(nodeId, attrsArray)
    {
        var node = this._idToDOMNode[nodeId];
        node._setAttributesPayload(attrsArray);
    },

    nodeForId: function(nodeId) {
        return this._idToDOMNode[nodeId];
    },

    _setDocument: function(payload)
    {
        this._idToDOMNode = {};
        if (payload) {
            this.document = new WebInspector.DOMDocument(this, this._window, payload);
            this._idToDOMNode[payload.id] = this.document;
            this._bindNodes(this.document.children);
        } else
            this.document = null;
        WebInspector.panels.elements.reset();
    },

    _setDetachedRoot: function(payload)
    {
        var root = new WebInspector.DOMNode(this.document, payload);
        this._idToDOMNode[payload.id] = root;
    },

    _setChildNodes: function(parentId, payloads)
    {
        var parent = this._idToDOMNode[parentId];
        parent._setChildrenPayload(payloads);
        this._bindNodes(parent.children);
    },

    _bindNodes: function(children)
    {
        for (var i = 0; i < children.length; ++i) {
            var child = children[i];
            this._idToDOMNode[child.id] = child;
            if (child.children)
                this._bindNodes(child.children);
        }
    },

    _childNodeCountUpdated: function(nodeId, newValue)
    {
        var node = this._idToDOMNode[nodeId];
        node._childNodeCount = newValue;
        var outline = WebInspector.panels.elements.treeOutline;
        var treeElement = outline.findTreeElement(node);
        if (treeElement)
            treeElement.hasChildren = newValue;
    },

    _childNodeInserted: function(parentId, prevId, payload)
    {
        var parent = this._idToDOMNode[parentId];
        var prev = this._idToDOMNode[prevId];
        var node = parent._insertChild(prev, payload);
        this._idToDOMNode[node.id] = node;
        var event = { target : node, relatedNode : parent };
        this.document._fireDomEvent("DOMNodeInserted", event);
    },

    _childNodeRemoved: function(parentId, nodeId)
    {
        var parent = this._idToDOMNode[parentId];
        var node = this._idToDOMNode[nodeId];
        parent.removeChild_(node);
        var event = { target : node, relatedNode : parent };
        this.document._fireDomEvent("DOMNodeRemoved", event);
        delete this._idToDOMNode[nodeId];
    }
}

WebInspector.Cookies = {}

WebInspector.Cookies.getCookiesAsync = function(callback, cookieDomain)
{
    function mycallback(cookies, cookiesString) {
        if (cookiesString)
            callback(WebInspector.Cookies.buildCookiesFromString(cookiesString), false);
        else
            callback(cookies, true);
    }
    var callId = WebInspector.Callback.wrap(mycallback);
    InspectorController.getCookies(callId, cookieDomain);
}

WebInspector.Cookies.buildCookiesFromString = function(rawCookieString)
{
    var rawCookies = rawCookieString.split(/;\s*/);
    var cookies = [];

    if (!(/^\s*$/.test(rawCookieString))) {
        for (var i = 0; i < rawCookies.length; ++i) {
            var cookie = rawCookies[i];
            var delimIndex = cookie.indexOf("=");
            var name = cookie.substring(0, delimIndex);
            var value = cookie.substring(delimIndex + 1);
            var size = name.length + value.length;
            cookies.push({ name: name, value: value, size: size });
        }
    }

    return cookies;
}

WebInspector.EventListeners = {}

WebInspector.EventListeners.getEventListenersForNodeAsync = function(node, callback)
{
    if (!node)
        return;

    var callId = WebInspector.Callback.wrap(callback);
    InspectorController.getEventListenersForNode(callId, node.id);
}

WebInspector.CSSStyleDeclaration = function(payload)
{
    this.id = payload.id;
    this.width = payload.width;
    this.height = payload.height;
    this.__disabledProperties = payload.__disabledProperties;
    this.__disabledPropertyValues = payload.__disabledPropertyValues;
    this.__disabledPropertyPriorities = payload.__disabledPropertyPriorities;
    this.uniqueStyleProperties = payload.uniqueStyleProperties;
    this._shorthandValues = payload.shorthandValues;
    this._propertyMap = {};
    this._longhandProperties = {};
    this.length = payload.properties.length;

    for (var i = 0; i < this.length; ++i) {
        var property = payload.properties[i];
        var name = property.name;
        this[i] = name;
        this._propertyMap[name] = property;
    }

    // Index longhand properties.
    for (var i = 0; i < this.uniqueStyleProperties.length; ++i) {
        var name = this.uniqueStyleProperties[i];
        var property = this._propertyMap[name];
        if (property.shorthand) {
            var longhands = this._longhandProperties[property.shorthand];
            if (!longhands) {
                longhands = [];
                this._longhandProperties[property.shorthand] = longhands;
            }
            longhands.push(name);
        }
    }
}

WebInspector.CSSStyleDeclaration.parseStyle = function(payload)
{
    return new WebInspector.CSSStyleDeclaration(payload);
}

WebInspector.CSSStyleDeclaration.parseRule = function(payload)
{
    var rule = {};
    rule.id = payload.id;
    rule.selectorText = payload.selectorText;
    rule.style = new WebInspector.CSSStyleDeclaration(payload.style);
    rule.style.parentRule = rule;
    rule.isUserAgent = payload.isUserAgent;
    rule.isUser = payload.isUser;
    rule.isViaInspector = payload.isViaInspector;
    if (payload.parentStyleSheet)
        rule.parentStyleSheet = { href: payload.parentStyleSheet.href };

    return rule;
}

WebInspector.CSSStyleDeclaration.prototype = {
    getPropertyValue: function(name)
    {
        var property = this._propertyMap[name];
        return property ? property.value : "";
    },

    getPropertyPriority: function(name)
    {
        var property = this._propertyMap[name];
        return property ? property.priority : "";
    },

    getPropertyShorthand: function(name)
    {
        var property = this._propertyMap[name];
        return property ? property.shorthand : "";
    },

    isPropertyImplicit: function(name)
    {
        var property = this._propertyMap[name];
        return property ? property.implicit : "";
    },

    styleTextWithShorthands: function()
    {
        var cssText = "";
        var foundProperties = {};
        for (var i = 0; i < this.length; ++i) {
            var individualProperty = this[i];
            var shorthandProperty = this.getPropertyShorthand(individualProperty);
            var propertyName = (shorthandProperty || individualProperty);

            if (propertyName in foundProperties)
                continue;

            if (shorthandProperty) {
                var value = this.getPropertyValue(shorthandProperty);
                var priority = this.getShorthandPriority(shorthandProperty);
            } else {
                var value = this.getPropertyValue(individualProperty);
                var priority = this.getPropertyPriority(individualProperty);
            }

            foundProperties[propertyName] = true;

            cssText += propertyName + ": " + value;
            if (priority)
                cssText += " !" + priority;
            cssText += "; ";
        }

        return cssText;
    },

    getLonghandProperties: function(name)
    {
        return this._longhandProperties[name] || [];
    },

    getShorthandValue: function(shorthandProperty)
    {
        return this._shorthandValues[shorthandProperty];
    },

    getShorthandPriority: function(shorthandProperty)
    {
        var priority = this.getPropertyPriority(shorthandProperty);
        if (priority)
            return priority;

        var longhands = this._longhandProperties[shorthandProperty];
        return longhands ? this.getPropertyPriority(longhands[0]) : null;
    }
}

WebInspector.attributesUpdated = function()
{
    this.domAgent._attributesUpdated.apply(this.domAgent, arguments);
}

WebInspector.setDocument = function()
{
    this.domAgent._setDocument.apply(this.domAgent, arguments);
}

WebInspector.setDetachedRoot = function()
{
    this.domAgent._setDetachedRoot.apply(this.domAgent, arguments);
}

WebInspector.setChildNodes = function()
{
    this.domAgent._setChildNodes.apply(this.domAgent, arguments);
}

WebInspector.childNodeCountUpdated = function()
{
    this.domAgent._childNodeCountUpdated.apply(this.domAgent, arguments);
}

WebInspector.childNodeInserted = function()
{
    this.domAgent._childNodeInserted.apply(this.domAgent, arguments);
}

WebInspector.childNodeRemoved = function()
{
    this.domAgent._childNodeRemoved.apply(this.domAgent, arguments);
}

WebInspector.didGetCookies = WebInspector.Callback.processCallback;
WebInspector.didGetChildNodes = WebInspector.Callback.processCallback;
WebInspector.didPerformSearch = WebInspector.Callback.processCallback;
WebInspector.didApplyDomChange = WebInspector.Callback.processCallback;
WebInspector.didRemoveAttribute = WebInspector.Callback.processCallback;
WebInspector.didSetTextNodeValue = WebInspector.Callback.processCallback;
WebInspector.didGetEventListenersForNode = WebInspector.Callback.processCallback;

/* InjectedScript.js */

/*
 * Copyright (C) 2007 Apple Inc.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

var InjectedScript = {};

// Called from within InspectorController on the 'inspected page' side.
InjectedScript.reset = function()
{
    InjectedScript._styles = {};
    InjectedScript._styleRules = {};
    InjectedScript._lastStyleId = 0;
    InjectedScript._lastStyleRuleId = 0;
    InjectedScript._searchResults = [];
    InjectedScript._includedInSearchResultsPropertyName = "__includedInInspectorSearchResults";
}

InjectedScript.reset();

InjectedScript.dispatch = function(methodName, args, callId)
{
    var argsArray = JSON.parse(args);
    if (callId)
        argsArray.splice(0, 0, callId);  // Methods that run asynchronously have a call back id parameter.
    var result = InjectedScript[methodName].apply(InjectedScript, argsArray);
    if (typeof result === "undefined") {
        InjectedScript._window().console.error("Web Inspector error: InjectedScript.%s returns undefined", methodName);
        result = null;
    }
    return JSON.stringify(result);
}

InjectedScript.getStyles = function(nodeId, authorOnly)
{
    var node = InjectedScript._nodeForId(nodeId);
    if (!node)
        return false;
    var defaultView = node.ownerDocument.defaultView;
    var matchedRules = defaultView.getMatchedCSSRules(node, "", authorOnly);
    var matchedCSSRules = [];
    for (var i = 0; matchedRules && i < matchedRules.length; ++i)
        matchedCSSRules.push(InjectedScript._serializeRule(matchedRules[i]));

    var styleAttributes = {};
    var attributes = node.attributes;
    for (var i = 0; attributes && i < attributes.length; ++i) {
        if (attributes[i].style)
            styleAttributes[attributes[i].name] = InjectedScript._serializeStyle(attributes[i].style, true);
    }
    var result = {};
    result.inlineStyle = InjectedScript._serializeStyle(node.style, true);
    result.computedStyle = InjectedScript._serializeStyle(defaultView.getComputedStyle(node));
    result.matchedCSSRules = matchedCSSRules;
    result.styleAttributes = styleAttributes;
    return result;
}

InjectedScript.getComputedStyle = function(nodeId)
{
    var node = InjectedScript._nodeForId(nodeId);
    if (!node)
        return false;
    return InjectedScript._serializeStyle(node.ownerDocument.defaultView.getComputedStyle(node));
}

InjectedScript.getInlineStyle = function(nodeId)
{
    var node = InjectedScript._nodeForId(nodeId);
    if (!node)
        return false;
    return InjectedScript._serializeStyle(node.style, true);
}

InjectedScript.applyStyleText = function(styleId, styleText, propertyName)
{
    var style = InjectedScript._styles[styleId];
    if (!style)
        return false;

    var styleTextLength = styleText.length;

    // Create a new element to parse the user input CSS.
    var parseElement = document.createElement("span");
    parseElement.setAttribute("style", styleText);

    var tempStyle = parseElement.style;
    if (tempStyle.length || !styleTextLength) {
        // The input was parsable or the user deleted everything, so remove the
        // original property from the real style declaration. If this represents
        // a shorthand remove all the longhand properties.
        if (style.getPropertyShorthand(propertyName)) {
            var longhandProperties = InjectedScript._getLonghandProperties(style, propertyName);
            for (var i = 0; i < longhandProperties.length; ++i)
                style.removeProperty(longhandProperties[i]);
        } else
            style.removeProperty(propertyName);
    }

    // Notify caller that the property was successfully deleted.
    if (!styleTextLength)
        return [null, [propertyName]];

    if (!tempStyle.length)
        return false;

    // Iterate of the properties on the test element's style declaration and
    // add them to the real style declaration. We take care to move shorthands.
    var foundShorthands = {};
    var changedProperties = [];
    var uniqueProperties = InjectedScript._getUniqueStyleProperties(tempStyle);
    for (var i = 0; i < uniqueProperties.length; ++i) {
        var name = uniqueProperties[i];
        var shorthand = tempStyle.getPropertyShorthand(name);

        if (shorthand && shorthand in foundShorthands)
            continue;

        if (shorthand) {
            var value = InjectedScript._getShorthandValue(tempStyle, shorthand);
            var priority = InjectedScript._getShorthandPriority(tempStyle, shorthand);
            foundShorthands[shorthand] = true;
        } else {
            var value = tempStyle.getPropertyValue(name);
            var priority = tempStyle.getPropertyPriority(name);
        }

        // Set the property on the real style declaration.
        style.setProperty((shorthand || name), value, priority);
        changedProperties.push(shorthand || name);
    }
    return [InjectedScript._serializeStyle(style, true), changedProperties];
}

InjectedScript.setStyleText = function(style, cssText)
{
    style.cssText = cssText;
    return true;
}

InjectedScript.toggleStyleEnabled = function(styleId, propertyName, disabled)
{
    var style = InjectedScript._styles[styleId];
    if (!style)
        return false;

    if (disabled) {
        if (!style.__disabledPropertyValues || !style.__disabledPropertyPriorities) {
            var inspectedWindow = InjectedScript._window();
            style.__disabledProperties = new inspectedWindow.Object;
            style.__disabledPropertyValues = new inspectedWindow.Object;
            style.__disabledPropertyPriorities = new inspectedWindow.Object;
        }

        style.__disabledPropertyValues[propertyName] = style.getPropertyValue(propertyName);
        style.__disabledPropertyPriorities[propertyName] = style.getPropertyPriority(propertyName);

        if (style.getPropertyShorthand(propertyName)) {
            var longhandProperties = InjectedScript._getLonghandProperties(style, propertyName);
            for (var i = 0; i < longhandProperties.length; ++i) {
                style.__disabledProperties[longhandProperties[i]] = true;
                style.removeProperty(longhandProperties[i]);
            }
        } else {
            style.__disabledProperties[propertyName] = true;
            style.removeProperty(propertyName);
        }
    } else if (style.__disabledProperties && style.__disabledProperties[propertyName]) {
        var value = style.__disabledPropertyValues[propertyName];
        var priority = style.__disabledPropertyPriorities[propertyName];

        style.setProperty(propertyName, value, priority);
        delete style.__disabledProperties[propertyName];
        delete style.__disabledPropertyValues[propertyName];
        delete style.__disabledPropertyPriorities[propertyName];
    }
    return InjectedScript._serializeStyle(style, true);
}

InjectedScript.applyStyleRuleText = function(ruleId, newContent, selectedNodeId)
{
    var rule = InjectedScript._styleRules[ruleId];
    if (!rule)
        return false;

    var selectedNode = InjectedScript._nodeForId(selectedNodeId);

    try {
        var stylesheet = rule.parentStyleSheet;
        stylesheet.addRule(newContent);
        var newRule = stylesheet.cssRules[stylesheet.cssRules.length - 1];
        newRule.style.cssText = rule.style.cssText;

        var parentRules = stylesheet.cssRules;
        for (var i = 0; i < parentRules.length; ++i) {
            if (parentRules[i] === rule) {
                rule.parentStyleSheet.removeRule(i);
                break;
            }
        }

        return [InjectedScript._serializeRule(newRule), InjectedScript._doesSelectorAffectNode(newContent, selectedNode)];
    } catch(e) {
        // Report invalid syntax.
        return false;
    }
}

InjectedScript.addStyleSelector = function(newContent, selectedNodeId)
{
    var selectedNode = InjectedScript._nodeForId(selectedNodeId);
    if (!selectedNode)
        return false;
    var ownerDocument = selectedNode.ownerDocument;

    var stylesheet = ownerDocument.__stylesheet;
    if (!stylesheet) {
        var head = ownerDocument.getElementsByTagName("head")[0];
        var styleElement = ownerDocument.createElement("style");
        styleElement.type = "text/css";
        head.appendChild(styleElement);
        stylesheet = ownerDocument.styleSheets[ownerDocument.styleSheets.length - 1];
        ownerDocument.__stylesheet = stylesheet;
    }

    try {
        stylesheet.addRule(newContent);
    } catch (e) {
        // Invalid Syntax for a Selector
        return false;
    }

    var rule = stylesheet.cssRules[stylesheet.cssRules.length - 1];
    rule.__isViaInspector = true;

    return [ InjectedScript._serializeRule(rule), InjectedScript._doesSelectorAffectNode(newContent, selectedNode) ];
}

InjectedScript._doesSelectorAffectNode = function(selectorText, node)
{
    if (!node)
        return false;
    var nodes = node.ownerDocument.querySelectorAll(selectorText);
    for (var i = 0; i < nodes.length; ++i) {
        if (nodes[i] === node) {
            return true;
        }
    }
    return false;
}

InjectedScript.setStyleProperty = function(styleId, name, value)
{
    var style = InjectedScript._styles[styleId];
    if (!style)
        return false;

    style.setProperty(name, value, "");
    return true;
}

InjectedScript._serializeRule = function(rule)
{
    var parentStyleSheet = rule.parentStyleSheet;

    var ruleValue = {};
    ruleValue.selectorText = rule.selectorText;
    if (parentStyleSheet) {
        ruleValue.parentStyleSheet = {};
        ruleValue.parentStyleSheet.href = parentStyleSheet.href;
    }
    ruleValue.isUserAgent = parentStyleSheet && !parentStyleSheet.ownerNode && !parentStyleSheet.href;
    ruleValue.isUser = parentStyleSheet && parentStyleSheet.ownerNode && parentStyleSheet.ownerNode.nodeName == "#document";
    ruleValue.isViaInspector = !!rule.__isViaInspector;

    // Bind editable scripts only.
    var doBind = !ruleValue.isUserAgent && !ruleValue.isUser;
    ruleValue.style = InjectedScript._serializeStyle(rule.style, doBind);

    if (doBind) {
        if (!rule.id) {
            rule.id = InjectedScript._lastStyleRuleId++;
            InjectedScript._styleRules[rule.id] = rule;
        }
        ruleValue.id = rule.id;
    }
    return ruleValue;
}

InjectedScript._serializeStyle = function(style, doBind)
{
    var result = {};
    result.width = style.width;
    result.height = style.height;
    result.__disabledProperties = style.__disabledProperties;
    result.__disabledPropertyValues = style.__disabledPropertyValues;
    result.__disabledPropertyPriorities = style.__disabledPropertyPriorities;
    result.properties = [];
    result.shorthandValues = {};
    var foundShorthands = {};
    for (var i = 0; i < style.length; ++i) {
        var property = {};
        var name = style[i];
        property.name = name;
        property.priority = style.getPropertyPriority(name);
        property.implicit = style.isPropertyImplicit(name);
        var shorthand =  style.getPropertyShorthand(name);
        property.shorthand = shorthand;
        if (shorthand && !(shorthand in foundShorthands)) {
            foundShorthands[shorthand] = true;
            result.shorthandValues[shorthand] = InjectedScript._getShorthandValue(style, shorthand);
        }
        property.value = style.getPropertyValue(name);
        result.properties.push(property);
    }
    result.uniqueStyleProperties = InjectedScript._getUniqueStyleProperties(style);

    if (doBind) {
        if (!style.id) {
            style.id = InjectedScript._lastStyleId++;
            InjectedScript._styles[style.id] = style;
        }
        result.id = style.id;
    }
    return result;
}

InjectedScript._getUniqueStyleProperties = function(style)
{
    var properties = [];
    var foundProperties = {};

    for (var i = 0; i < style.length; ++i) {
        var property = style[i];
        if (property in foundProperties)
            continue;
        foundProperties[property] = true;
        properties.push(property);
    }

    return properties;
}


InjectedScript._getLonghandProperties = function(style, shorthandProperty)
{
    var properties = [];
    var foundProperties = {};

    for (var i = 0; i < style.length; ++i) {
        var individualProperty = style[i];
        if (individualProperty in foundProperties || style.getPropertyShorthand(individualProperty) !== shorthandProperty)
            continue;
        foundProperties[individualProperty] = true;
        properties.push(individualProperty);
    }

    return properties;
}

InjectedScript._getShorthandValue = function(style, shorthandProperty)
{
    var value = style.getPropertyValue(shorthandProperty);
    if (!value) {
        // Some shorthands (like border) return a null value, so compute a shorthand value.
        // FIXME: remove this when http://bugs.webkit.org/show_bug.cgi?id=15823 is fixed.

        var foundProperties = {};
        for (var i = 0; i < style.length; ++i) {
            var individualProperty = style[i];
            if (individualProperty in foundProperties || style.getPropertyShorthand(individualProperty) !== shorthandProperty)
                continue;

            var individualValue = style.getPropertyValue(individualProperty);
            if (style.isPropertyImplicit(individualProperty) || individualValue === "initial")
                continue;

            foundProperties[individualProperty] = true;

            if (!value)
                value = "";
            else if (value.length)
                value += " ";
            value += individualValue;
        }
    }
    return value;
}

InjectedScript._getShorthandPriority = function(style, shorthandProperty)
{
    var priority = style.getPropertyPriority(shorthandProperty);
    if (!priority) {
        for (var i = 0; i < style.length; ++i) {
            var individualProperty = style[i];
            if (style.getPropertyShorthand(individualProperty) !== shorthandProperty)
                continue;
            priority = style.getPropertyPriority(individualProperty);
            break;
        }
    }
    return priority;
}

InjectedScript.getPrototypes = function(nodeId)
{
    var node = InjectedScript._nodeForId(nodeId);
    if (!node)
        return false;

    var result = [];
    for (var prototype = node; prototype; prototype = prototype.__proto__) {
        var title = Object.describe(prototype, true);
        if (title.match(/Prototype$/)) {
            title = title.replace(/Prototype$/, "");
        }
        result.push(title);
    }
    return result;
}

InjectedScript.getProperties = function(objectProxy, ignoreHasOwnProperty)
{
    var object = InjectedScript._resolveObject(objectProxy);
    if (!object)
        return false;

    var properties = [];

    // Go over properties, prepare results.
    for (var propertyName in object) {
        if (!ignoreHasOwnProperty && "hasOwnProperty" in object && !object.hasOwnProperty(propertyName))
            continue;

        var property = {};
        property.name = propertyName;
        property.parentObjectProxy = objectProxy;
        var isGetter = object["__lookupGetter__"] && object.__lookupGetter__(propertyName);
        if (!property.isGetter) {
            var childObject = object[propertyName];
            var childObjectProxy = new InjectedScript.createProxyObject(childObject, objectProxy.objectId, true);
            childObjectProxy.path = objectProxy.path ? objectProxy.path.slice() : [];
            childObjectProxy.path.push(propertyName);
            childObjectProxy.protoDepth = objectProxy.protoDepth || 0;
            property.value = childObjectProxy;
        } else {
            // FIXME: this should show something like "getter" (bug 16734).
            property.value = { description: "\u2014" }; // em dash
            property.isGetter = true;
        }
        properties.push(property);
    }
    return properties;
}

InjectedScript.setPropertyValue = function(objectProxy, propertyName, expression)
{
    var object = InjectedScript._resolveObject(objectProxy);
    if (!object)
        return false;

    var expressionLength = expression.length;
    if (!expressionLength) {
        delete object[propertyName];
        return !(propertyName in object);
    }

    try {
        // Surround the expression in parenthesis so the result of the eval is the result
        // of the whole expression not the last potential sub-expression.

        // There is a regression introduced here: eval is now happening against global object,
        // not call frame while on a breakpoint.
        // TODO: bring evaluation against call frame back.
        var result = InjectedScript._window().eval("(" + expression + ")");
        // Store the result in the property.
        object[propertyName] = result;
        return true;
    } catch(e) {
        try {
            var result = InjectedScript._window().eval("\"" + expression.escapeCharacters("\"") + "\"");
            object[propertyName] = result;
            return true;
        } catch(e) {
            return false;
        }
    }
}


InjectedScript.getCompletions = function(expression, includeInspectorCommandLineAPI, callFrameId)
{
    var props = {};
    try {
        var expressionResult;
        // Evaluate on call frame if call frame id is available.
        if (typeof callFrameId === "number") {
            var callFrame = InjectedScript._callFrameForId(callFrameId);
            if (!callFrame)
                return props;
            if (expression)
                expressionResult = InjectedScript._evaluateOn(callFrame.evaluate, callFrame, expression);
            else {
                // Evaluate into properties in scope of the selected call frame.
                var scopeChain = callFrame.scopeChain;
                for (var i = 0; i < scopeChain.length; ++i) {
                    var scopeObject = scopeChain[i];
                    try {
                        for (var propertyName in scopeObject)
                            props[propertyName] = true;
                    } catch (e) {
                    }
                }
            }
        } else {
            if (!expression)
                expression = "this";
            expressionResult = InjectedScript._evaluateOn(InjectedScript._window().eval, InjectedScript._window(), expression);
        }
        if (expressionResult)
            for (var prop in expressionResult)
                props[prop] = true;
        if (includeInspectorCommandLineAPI)
            for (var prop in InjectedScript._window().console._inspectorCommandLineAPI)
                if (prop.charAt(0) !== '_')
                    props[prop] = true;
    } catch(e) {
    }
    return props;
}

InjectedScript.evaluate = function(expression, objectGroup)
{
    return InjectedScript._evaluateAndWrap(InjectedScript._window().eval, InjectedScript._window(), expression, objectGroup);
}

InjectedScript._evaluateAndWrap = function(evalFunction, object, expression, objectGroup)
{
    var result = {};
    try {
        result.value = InspectorController.wrapObject(InjectedScript._evaluateOn(evalFunction, object, expression), objectGroup);
        // Handle error that might have happened while describing result.
        if (result.value.errorText) {
            result.value = result.value.errorText;
            result.isException = true;
        }
    } catch (e) {
        result.value = e.toString();
        result.isException = true;
    }
    return result;
}

InjectedScript._evaluateOn = function(evalFunction, object, expression)
{
    InjectedScript._ensureCommandLineAPIInstalled(evalFunction, object);
    // Surround the expression in with statements to inject our command line API so that
    // the window object properties still take more precedent than our API functions.
    expression = "with (window.console._inspectorCommandLineAPI) { with (window) { " + expression + " } }";
    var value = evalFunction.call(object, expression);

    // When evaluating on call frame error is not thrown, but returned as a value.
    if (Object.type(value) === "error")
        throw value.toString();

    return value;
}

InjectedScript.addInspectedNode = function(nodeId)
{
    var node = InjectedScript._nodeForId(nodeId);
    if (!node)
        return false;

    InjectedScript._ensureCommandLineAPIInstalled(InjectedScript._window().eval, InjectedScript._window());
    var inspectedNodes = InjectedScript._window().console._inspectorCommandLineAPI._inspectedNodes;
    inspectedNodes.unshift(node);
    if (inspectedNodes.length >= 5)
        inspectedNodes.pop();
    return true;
}

InjectedScript.performSearch = function(whitespaceTrimmedQuery)
{
    var tagNameQuery = whitespaceTrimmedQuery;
    var attributeNameQuery = whitespaceTrimmedQuery;
    var startTagFound = (tagNameQuery.indexOf("<") === 0);
    var endTagFound = (tagNameQuery.lastIndexOf(">") === (tagNameQuery.length - 1));

    if (startTagFound || endTagFound) {
        var tagNameQueryLength = tagNameQuery.length;
        tagNameQuery = tagNameQuery.substring((startTagFound ? 1 : 0), (endTagFound ? (tagNameQueryLength - 1) : tagNameQueryLength));
    }

    // Check the tagNameQuery is it is a possibly valid tag name.
    if (!/^[a-zA-Z0-9\-_:]+$/.test(tagNameQuery))
        tagNameQuery = null;

    // Check the attributeNameQuery is it is a possibly valid tag name.
    if (!/^[a-zA-Z0-9\-_:]+$/.test(attributeNameQuery))
        attributeNameQuery = null;

    const escapedQuery = whitespaceTrimmedQuery.escapeCharacters("'");
    const escapedTagNameQuery = (tagNameQuery ? tagNameQuery.escapeCharacters("'") : null);
    const escapedWhitespaceTrimmedQuery = whitespaceTrimmedQuery.escapeCharacters("'");
    const searchResultsProperty = InjectedScript._includedInSearchResultsPropertyName;

    function addNodesToResults(nodes, length, getItem)
    {
        if (!length)
            return;

        var nodeIds = [];
        for (var i = 0; i < length; ++i) {
            var node = getItem.call(nodes, i);
            // Skip this node if it already has the property.
            if (searchResultsProperty in node)
                continue;

            if (!InjectedScript._searchResults.length) {
                InjectedScript._currentSearchResultIndex = 0;
            }

            node[searchResultsProperty] = true;
            InjectedScript._searchResults.push(node);
            var nodeId = InspectorController.pushNodePathToFrontend(node, false);
            nodeIds.push(nodeId);
        }
        InspectorController.addNodesToSearchResult(nodeIds.join(","));
    }

    function matchExactItems(doc)
    {
        matchExactId.call(this, doc);
        matchExactClassNames.call(this, doc);
        matchExactTagNames.call(this, doc);
        matchExactAttributeNames.call(this, doc);
    }

    function matchExactId(doc)
    {
        const result = doc.__proto__.getElementById.call(doc, whitespaceTrimmedQuery);
        addNodesToResults.call(this, result, (result ? 1 : 0), function() { return this });
    }

    function matchExactClassNames(doc)
    {
        const result = doc.__proto__.getElementsByClassName.call(doc, whitespaceTrimmedQuery);
        addNodesToResults.call(this, result, result.length, result.item);
    }

    function matchExactTagNames(doc)
    {
        if (!tagNameQuery)
            return;
        const result = doc.__proto__.getElementsByTagName.call(doc, tagNameQuery);
        addNodesToResults.call(this, result, result.length, result.item);
    }

    function matchExactAttributeNames(doc)
    {
        if (!attributeNameQuery)
            return;
        const result = doc.__proto__.querySelectorAll.call(doc, "[" + attributeNameQuery + "]");
        addNodesToResults.call(this, result, result.length, result.item);
    }

    function matchPartialTagNames(doc)
    {
        if (!tagNameQuery)
            return;
        const result = doc.__proto__.evaluate.call(doc, "//*[contains(name(), '" + escapedTagNameQuery + "')]", doc, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE);
        addNodesToResults.call(this, result, result.snapshotLength, result.snapshotItem);
    }

    function matchStartOfTagNames(doc)
    {
        if (!tagNameQuery)
            return;
        const result = doc.__proto__.evaluate.call(doc, "//*[starts-with(name(), '" + escapedTagNameQuery + "')]", doc, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE);
        addNodesToResults.call(this, result, result.snapshotLength, result.snapshotItem);
    }

    function matchPartialTagNamesAndAttributeValues(doc)
    {
        if (!tagNameQuery) {
            matchPartialAttributeValues.call(this, doc);
            return;
        }

        const result = doc.__proto__.evaluate.call(doc, "//*[contains(name(), '" + escapedTagNameQuery + "') or contains(@*, '" + escapedQuery + "')]", doc, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE);
        addNodesToResults.call(this, result, result.snapshotLength, result.snapshotItem);
    }

    function matchPartialAttributeValues(doc)
    {
        const result = doc.__proto__.evaluate.call(doc, "//*[contains(@*, '" + escapedQuery + "')]", doc, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE);
        addNodesToResults.call(this, result, result.snapshotLength, result.snapshotItem);
    }

    function matchStyleSelector(doc)
    {
        const result = doc.__proto__.querySelectorAll.call(doc, whitespaceTrimmedQuery);
        addNodesToResults.call(this, result, result.length, result.item);
    }

    function matchPlainText(doc)
    {
        const result = doc.__proto__.evaluate.call(doc, "//text()[contains(., '" + escapedQuery + "')] | //comment()[contains(., '" + escapedQuery + "')]", doc, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE);
        addNodesToResults.call(this, result, result.snapshotLength, result.snapshotItem);
    }

    function matchXPathQuery(doc)
    {
        const result = doc.__proto__.evaluate.call(doc, whitespaceTrimmedQuery, doc, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE);
        addNodesToResults.call(this, result, result.snapshotLength, result.snapshotItem);
    }

    function finishedSearching()
    {
        // Remove the searchResultsProperty now that the search is finished.
        for (var i = 0; i < InjectedScript._searchResults.length; ++i)
            delete InjectedScript._searchResults[i][searchResultsProperty];
    }

    const mainFrameDocument = InjectedScript._window().document;
    const searchDocuments = [mainFrameDocument];
    var searchFunctions;
    if (tagNameQuery && startTagFound && endTagFound)
        searchFunctions = [matchExactTagNames, matchPlainText];
    else if (tagNameQuery && startTagFound)
        searchFunctions = [matchStartOfTagNames, matchPlainText];
    else if (tagNameQuery && endTagFound) {
        // FIXME: we should have a matchEndOfTagNames search function if endTagFound is true but not startTagFound.
        // This requires ends-with() support in XPath, WebKit only supports starts-with() and contains().
        searchFunctions = [matchPartialTagNames, matchPlainText];
    } else if (whitespaceTrimmedQuery === "//*" || whitespaceTrimmedQuery === "*") {
        // These queries will match every node. Matching everything isn't useful and can be slow for large pages,
        // so limit the search functions list to plain text and attribute matching.
        searchFunctions = [matchPartialAttributeValues, matchPlainText];
    } else
        searchFunctions = [matchExactItems, matchStyleSelector, matchPartialTagNamesAndAttributeValues, matchPlainText, matchXPathQuery];

    // Find all frames, iframes and object elements to search their documents.
    const querySelectorAllFunction = InjectedScript._window().Document.prototype.querySelectorAll;
    const subdocumentResult = querySelectorAllFunction.call(mainFrameDocument, "iframe, frame, object");

    for (var i = 0; i < subdocumentResult.length; ++i) {
        var element = subdocumentResult.item(i);
        if (element.contentDocument)
            searchDocuments.push(element.contentDocument);
    }

    const panel = InjectedScript;
    var documentIndex = 0;
    var searchFunctionIndex = 0;
    var chunkIntervalIdentifier = null;

    // Split up the work into chunks so we don't block the UI thread while processing.

    function processChunk()
    {
        var searchDocument = searchDocuments[documentIndex];
        var searchFunction = searchFunctions[searchFunctionIndex];

        if (++searchFunctionIndex > searchFunctions.length) {
            searchFunction = searchFunctions[0];
            searchFunctionIndex = 0;

            if (++documentIndex > searchDocuments.length) {
                if (panel._currentSearchChunkIntervalIdentifier === chunkIntervalIdentifier)
                    delete panel._currentSearchChunkIntervalIdentifier;
                clearInterval(chunkIntervalIdentifier);
                finishedSearching.call(panel);
                return;
            }

            searchDocument = searchDocuments[documentIndex];
        }

        if (!searchDocument || !searchFunction)
            return;

        try {
            searchFunction.call(panel, searchDocument);
        } catch(err) {
            // ignore any exceptions. the query might be malformed, but we allow that.
        }
    }

    processChunk();

    chunkIntervalIdentifier = setInterval(processChunk, 25);
    InjectedScript._currentSearchChunkIntervalIdentifier = chunkIntervalIdentifier;
    return true;
}

InjectedScript.searchCanceled = function()
{
    if (InjectedScript._searchResults) {
        const searchResultsProperty = InjectedScript._includedInSearchResultsPropertyName;
        for (var i = 0; i < this._searchResults.length; ++i) {
            var node = this._searchResults[i];

            // Remove the searchResultsProperty since there might be an unfinished search.
            delete node[searchResultsProperty];
        }
    }

    if (InjectedScript._currentSearchChunkIntervalIdentifier) {
        clearInterval(InjectedScript._currentSearchChunkIntervalIdentifier);
        delete InjectedScript._currentSearchChunkIntervalIdentifier;
    }
    InjectedScript._searchResults = [];
    return true;
}

InjectedScript.openInInspectedWindow = function(url)
{
    // Don't call window.open on wrapper - popup blocker mutes it.
    // URIs should have no double quotes.
    InjectedScript._window().eval("window.open(\"" + url + "\")");
    return true;
}

InjectedScript.getCallFrames = function()
{
    var callFrame = InspectorController.currentCallFrame();
    if (!callFrame)
        return false;

    var result = [];
    var depth = 0;
    do {
        result.push(new InjectedScript.CallFrameProxy(depth++, callFrame));
        callFrame = callFrame.caller;
    } while (callFrame);
    return result;
}

InjectedScript.evaluateInCallFrame = function(callFrameId, code, objectGroup)
{
    var callFrame = InjectedScript._callFrameForId(callFrameId);
    if (!callFrame)
        return false;
    return InjectedScript._evaluateAndWrap(callFrame.evaluate, callFrame, code, objectGroup);
}

InjectedScript._callFrameForId = function(id)
{
    var callFrame = InspectorController.currentCallFrame();
    while (--id >= 0 && callFrame)
        callFrame = callFrame.caller;
    return callFrame;
}

InjectedScript._clearConsoleMessages = function()
{
    InspectorController.clearMessages(true);
}

InjectedScript._inspectObject = function(o)
{
    if (arguments.length === 0)
        return;

    var inspectedWindow = InjectedScript._window();
    inspectedWindow.console.log(o);
    if (Object.type(o) === "node") {
        InspectorController.pushNodePathToFrontend(o, true);
    } else {
        switch (Object.describe(o)) {
            case "Database":
                InspectorController.selectDatabase(o);
                break;
            case "Storage":
                InspectorController.selectDOMStorage(o);
                break;
        }
    }
}

InjectedScript._copy = function(o)
{
    if (Object.type(o) === "node") {
        var nodeId = InspectorController.pushNodePathToFrontend(o, false);
        InspectorController.copyNode(nodeId);
    } else {
        InspectorController.copyText(o);
    }
}

InjectedScript._ensureCommandLineAPIInstalled = function(evalFunction, evalObject)
{
    if (evalFunction.call(evalObject, "window.console._inspectorCommandLineAPI"))
        return;
    var inspectorCommandLineAPI = evalFunction.call(evalObject, "window.console._inspectorCommandLineAPI = { \n\
        $: function() { return document.getElementById.apply(document, arguments) }, \n\
        $$: function() { return document.querySelectorAll.apply(document, arguments) }, \n\
        $x: function(xpath, context) \n\
        { \n\
            var nodes = []; \n\
            try { \n\
                var doc = context || document; \n\
                var results = doc.evaluate(xpath, doc, null, XPathResult.ANY_TYPE, null); \n\
                var node; \n\
                while (node = results.iterateNext()) nodes.push(node); \n\
            } catch (e) {} \n\
            return nodes; \n\
        }, \n\
        dir: function() { return console.dir.apply(console, arguments) }, \n\
        dirxml: function() { return console.dirxml.apply(console, arguments) }, \n\
        keys: function(o) { var a = []; for (var k in o) a.push(k); return a; }, \n\
        values: function(o) { var a = []; for (var k in o) a.push(o[k]); return a; }, \n\
        profile: function() { return console.profile.apply(console, arguments) }, \n\
        profileEnd: function() { return console.profileEnd.apply(console, arguments) }, \n\
        _logEvent: function _inspectorCommandLineAPI_logEvent(e) { console.log(e.type, e); }, \n\
        _allEventTypes: [\"mouse\", \"key\", \"load\", \"unload\", \"abort\", \"error\", \n\
            \"select\", \"change\", \"submit\", \"reset\", \"focus\", \"blur\", \n\
            \"resize\", \"scroll\"], \n\
        _normalizeEventTypes: function(t) \n\
        { \n\
            if (typeof t === \"undefined\") \n\
                t = console._inspectorCommandLineAPI._allEventTypes; \n\
            else if (typeof t === \"string\") \n\
                t = [t]; \n\
            var i, te = []; \n\
            for (i = 0; i < t.length; i++) { \n\
                if (t[i] === \"mouse\") \n\
                    te.splice(0, 0, \"mousedown\", \"mouseup\", \"click\", \"dblclick\", \n\
                        \"mousemove\", \"mouseover\", \"mouseout\"); \n\
                else if (t[i] === \"key\") \n\
                    te.splice(0, 0, \"keydown\", \"keyup\", \"keypress\"); \n\
                else \n\
                    te.push(t[i]); \n\
            } \n\
            return te; \n\
        }, \n\
        monitorEvents: function(o, t) \n\
        { \n\
            if (!o || !o.addEventListener || !o.removeEventListener) \n\
                return; \n\
            t = console._inspectorCommandLineAPI._normalizeEventTypes(t); \n\
            for (i = 0; i < t.length; i++) { \n\
                o.removeEventListener(t[i], console._inspectorCommandLineAPI._logEvent, false); \n\
                o.addEventListener(t[i], console._inspectorCommandLineAPI._logEvent, false); \n\
            } \n\
        }, \n\
        unmonitorEvents: function(o, t) \n\
        { \n\
            if (!o || !o.removeEventListener) \n\
                return; \n\
            t = console._inspectorCommandLineAPI._normalizeEventTypes(t); \n\
            for (i = 0; i < t.length; i++) { \n\
                o.removeEventListener(t[i], console._inspectorCommandLineAPI._logEvent, false); \n\
            } \n\
        }, \n\
        _inspectedNodes: [], \n\
        get $0() { return console._inspectorCommandLineAPI._inspectedNodes[0] }, \n\
        get $1() { return console._inspectorCommandLineAPI._inspectedNodes[1] }, \n\
        get $2() { return console._inspectorCommandLineAPI._inspectedNodes[2] }, \n\
        get $3() { return console._inspectorCommandLineAPI._inspectedNodes[3] }, \n\
        get $4() { return console._inspectorCommandLineAPI._inspectedNodes[4] }, \n\
    };");

    inspectorCommandLineAPI.clear = InspectorController.wrapCallback(InjectedScript._clearConsoleMessages);
    inspectorCommandLineAPI.inspect = InspectorController.wrapCallback(InjectedScript._inspectObject);
    inspectorCommandLineAPI.copy = InspectorController.wrapCallback(InjectedScript._copy);
}

InjectedScript._resolveObject = function(objectProxy)
{
    var object = InjectedScript._objectForId(objectProxy.objectId);
    var path = objectProxy.path;
    var protoDepth = objectProxy.protoDepth;

    // Follow the property path.
    for (var i = 0; object && path && i < path.length; ++i)
        object = object[path[i]];

    // Get to the necessary proto layer.
    for (var i = 0; object && protoDepth && i < protoDepth; ++i)
        object = object.__proto__;

    return object;
}

InjectedScript._window = function()
{
    // TODO: replace with 'return window;' once this script is injected into
    // the page's context.
    return InspectorController.inspectedWindow();
}

InjectedScript._nodeForId = function(nodeId)
{
    if (!nodeId)
        return null;
    return InspectorController.nodeForId(nodeId);
}

InjectedScript._objectForId = function(objectId)
{
    // There are three types of object ids used:
    // - numbers point to DOM Node via the InspectorDOMAgent mapping
    // - strings point to console objects cached in InspectorController for lazy evaluation upon them
    // - objects contain complex ids and are currently used for scoped objects
    if (typeof objectId === "number") {
        return InjectedScript._nodeForId(objectId);
    } else if (typeof objectId === "string") {
        return InspectorController.unwrapObject(objectId);
    } else if (typeof objectId === "object") {
        var callFrame = InjectedScript._callFrameForId(objectId.callFrame);
        if (objectId.thisObject)
            return callFrame.thisObject;
        else
            return callFrame.scopeChain[objectId.chainIndex];
    }
    return objectId;
}

InjectedScript.pushNodeToFrontend = function(objectProxy)
{
    var object = InjectedScript._resolveObject(objectProxy);
    if (!object || Object.type(object) !== "node")
        return false;
    return InspectorController.pushNodePathToFrontend(object, false);
}

// Called from within InspectorController on the 'inspected page' side.
InjectedScript.createProxyObject = function(object, objectId, abbreviate)
{
    var result = {};
    result.objectId = objectId;
    result.type = Object.type(object);

    var type = typeof object;
    if ((type === "object" && object !== null) || type === "function") {
        for (var subPropertyName in object) {
            result.hasChildren = true;
            break;
        }
    }
    try {
        result.description = Object.describe(object, abbreviate);
    } catch (e) {
        result.errorText = e.toString();
    }
    return result;
}

InjectedScript.CallFrameProxy = function(id, callFrame)
{
    this.id = id;
    this.type = callFrame.type;
    this.functionName = (this.type === "function" ? callFrame.functionName : "");
    this.sourceID = callFrame.sourceID;
    this.line = callFrame.line;
    this.scopeChain = this._wrapScopeChain(callFrame);
}

InjectedScript.CallFrameProxy.prototype = {
    _wrapScopeChain: function(callFrame)
    {
        var foundLocalScope = false;
        var scopeChain = callFrame.scopeChain;
        var scopeChainProxy = [];
        for (var i = 0; i < scopeChain.length; ++i) {
            var scopeObject = scopeChain[i];
            var scopeObjectProxy = InjectedScript.createProxyObject(scopeObject, { callFrame: this.id, chainIndex: i }, true);

            if (Object.prototype.toString.call(scopeObject) === "[object JSActivation]") {
                if (!foundLocalScope)
                    scopeObjectProxy.thisObject = InjectedScript.createProxyObject(callFrame.thisObject, { callFrame: this.id, thisObject: true }, true);
                else
                    scopeObjectProxy.isClosure = true;
                foundLocalScope = true;
                scopeObjectProxy.isLocal = true;
            } else if (foundLocalScope && scopeObject instanceof InjectedScript._window().Element)
                scopeObjectProxy.isElement = true;
            else if (foundLocalScope && scopeObject instanceof InjectedScript._window().Document)
                scopeObjectProxy.isDocument = true;
            else if (!foundLocalScope)
                scopeObjectProxy.isWithBlock = true;
            scopeChainProxy.push(scopeObjectProxy);
        }
        return scopeChainProxy;
    }
}

InjectedScript.executeSql = function(callId, databaseId, query)
{
    function successCallback(tx, result)
    {
        var rows = result.rows;
        var result = [];
        var length = rows.length;
        for (var i = 0; i < length; ++i) {
            var data = {};
            result.push(data);
            var row = rows.item(i);
            for (var columnIdentifier in row) {
                // FIXME: (Bug 19439) We should specially format SQL NULL here
                // (which is represented by JavaScript null here, and turned
                // into the string "null" by the String() function).
                var text = row[columnIdentifier];
                data[columnIdentifier] = String(text);
            }
        }
        InspectorController.reportDidDispatchOnInjectedScript(callId, JSON.stringify(result), false);
    }

    function errorCallback(tx, error)
    {
        InspectorController.reportDidDispatchOnInjectedScript(callId, JSON.stringify(error), false);
    }

    function queryTransaction(tx)
    {
        tx.executeSql(query, null, InspectorController.wrapCallback(successCallback), InspectorController.wrapCallback(errorCallback));
    }

    var database = InspectorController.databaseForId(databaseId);
    if (!database)
        errorCallback(null, { code : 2 });  // Return as unexpected version.
    database.transaction(InspectorController.wrapCallback(queryTransaction), InspectorController.wrapCallback(errorCallback));
    return true;
}

Object.type = function(obj)
{
    if (obj === null)
        return "null";

    var type = typeof obj;
    if (type !== "object" && type !== "function")
        return type;

    var win = InjectedScript._window();

    if (obj instanceof win.Node)
        return (obj.nodeType === undefined ? type : "node");
    if (obj instanceof win.String)
        return "string";
    if (obj instanceof win.Array)
        return "array";
    if (obj instanceof win.Boolean)
        return "boolean";
    if (obj instanceof win.Number)
        return "number";
    if (obj instanceof win.Date)
        return "date";
    if (obj instanceof win.RegExp)
        return "regexp";
    if (obj instanceof win.NodeList)
        return "array";
    if (obj instanceof win.HTMLCollection || obj instanceof win.HTMLAllCollection)
        return "array";
    if (obj instanceof win.Error)
        return "error";
    return type;
}

Object.hasProperties = function(obj)
{
    if (typeof obj === "undefined" || typeof obj === "null")
        return false;
    for (var name in obj)
        return true;
    return false;
}

Object.describe = function(obj, abbreviated)
{
    var type1 = Object.type(obj);
    var type2 = Object.className(obj);

    switch (type1) {
    case "object":
    case "node":
    case "array":
        return type2;
    case "string":
        if (!abbreviated)
            return obj;
        if (obj.length > 100)
            return "\"" + obj.substring(0, 100) + "\u2026\"";
        return "\"" + obj + "\"";
    case "function":
        var objectText = String(obj);
        if (!/^function /.test(objectText))
            objectText = (type2 == "object") ? type1 : type2;
        else if (abbreviated)
            objectText = /.*/.exec(obj)[0].replace(/ +$/g, "");
        return objectText;
    case "regexp":
        return String(obj).replace(/([\\\/])/g, "\\$1").replace(/\\(\/[gim]*)$/, "$1").substring(1);
    default:
        return String(obj);
    }
}

Object.className = function(obj)
{
    return Object.prototype.toString.call(obj).replace(/^\[object (.*)\]$/i, "$1")
}

// Although Function.prototype.bind and String.prototype.escapeCharacters are defined in utilities.js they will soon become
// unavailable in the InjectedScript context. So we define them here for the local use.
// TODO: remove this comment once InjectedScript runs in a separate context.
Function.prototype.bind = function(thisObject)
{
    var func = this;
    var args = Array.prototype.slice.call(arguments, 1);
    return function() { return func.apply(thisObject, args.concat(Array.prototype.slice.call(arguments, 0))) };
}

String.prototype.escapeCharacters = function(chars)
{
    var foundChar = false;
    for (var i = 0; i < chars.length; ++i) {
        if (this.indexOf(chars.charAt(i)) !== -1) {
            foundChar = true;
            break;
        }
    }

    if (!foundChar)
        return this;

    var result = "";
    for (var i = 0; i < this.length; ++i) {
        if (chars.indexOf(this.charAt(i)) !== -1)
            result += "\\";
        result += this.charAt(i);
    }

    return result;
}

/* InjectedScriptAccess.js */

/*
 * Copyright (C) 2009 Google Inc. All rights reserved.
 * Copyright (C) 2009 Joseph Pecoraro
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

var InjectedScriptAccess = {};

InjectedScriptAccess._installHandler = function(methodName, async)
{
    InjectedScriptAccess[methodName] = function()
    {
        var allArgs = Array.prototype.slice.call(arguments);
        var callback = allArgs[allArgs.length - 1];
        var argsString = JSON.stringify(Array.prototype.slice.call(allArgs, 0, allArgs.length - 1));
        
        function myCallback(result, isException)
        {
            if (!isException)
                callback(JSON.parse(result));
            else
                WebInspector.console.addMessage(new WebInspector.ConsoleTextMessage("Error dispatching: " + methodName));
        }
        var callId = WebInspector.Callback.wrap(myCallback);
        InspectorController.dispatchOnInjectedScript(callId, methodName, argsString, !!async);
    };
}

// InjectedScriptAccess message forwarding puts some constraints on the way methods are imlpemented and called:
// - Make sure corresponding methods in InjectedScript return non-null and non-undefined values,
// - Make sure last parameter of all the InjectedSriptAccess.* calls is a callback function.
// We keep these sorted.
InjectedScriptAccess._installHandler("addInspectedNode");
InjectedScriptAccess._installHandler("addStyleSelector");
InjectedScriptAccess._installHandler("applyStyleRuleText");
InjectedScriptAccess._installHandler("applyStyleText");
InjectedScriptAccess._installHandler("evaluate");
InjectedScriptAccess._installHandler("evaluateInCallFrame");
InjectedScriptAccess._installHandler("getCompletions");
InjectedScriptAccess._installHandler("getComputedStyle");
InjectedScriptAccess._installHandler("getInlineStyle");
InjectedScriptAccess._installHandler("getProperties");
InjectedScriptAccess._installHandler("getPrototypes");
InjectedScriptAccess._installHandler("getStyles");
InjectedScriptAccess._installHandler("openInInspectedWindow");
InjectedScriptAccess._installHandler("performSearch");
InjectedScriptAccess._installHandler("pushNodeToFrontend");
InjectedScriptAccess._installHandler("searchCanceled");
InjectedScriptAccess._installHandler("setPropertyValue");
InjectedScriptAccess._installHandler("setStyleProperty");
InjectedScriptAccess._installHandler("setStyleText");
InjectedScriptAccess._installHandler("toggleStyleEnabled");

// Some methods can't run synchronously even on the injected script side (such as DB transactions).
// Mark them as asynchronous here.
InjectedScriptAccess._installHandler("executeSql", true);

WebInspector.didDispatchOnInjectedScript = WebInspector.Callback.processCallback;

/* TimelineAgent.js */

/*
 * Copyright (C) 2009 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.TimelineAgent = function() {
    // Not implemented.
}

// Must be kept in sync with TimelineItem.h
WebInspector.TimelineAgent.RecordType = {
    EventDispatch : 0,
    Layout : 1,
    RecalculateStyles : 2,
    Paint : 3,
    ParseHTML : 4,
    TimerInstall : 5,
    TimerRemove : 6,
    TimerFire : 7,
    XHRReadyStateChange : 8,
    XHRLoad : 9,
    EvaluateScript : 10,
    MarkTimeline : 11,
    ResourceSendRequest : 12,
    ResourceReceiveResponse : 13,
    ResourceFinish : 14
};

WebInspector.addRecordToTimeline = function(record) {
    if (WebInspector.panels.timeline)
        WebInspector.panels.timeline.addRecordToTimeline(record);
}

WebInspector.timelineProfilerWasStarted = function() {
    if (WebInspector.panels.timeline)
        WebInspector.panels.timeline.timelineWasStarted();
}

WebInspector.timelineProfilerWasStopped = function() {
    if (WebInspector.panels.timeline)
        WebInspector.panels.timeline.timelineWasStopped();
}

/* TimelinePanel.js */

/*
 * Copyright (C) 2009 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.TimelinePanel = function()
{
    WebInspector.AbstractTimelinePanel.call(this);

    this.element.addStyleClass("timeline");

    this._createOverview();
    this.createInterface();
    this.containerElement.id = "timeline-container";
    this.summaryBar.element.id = "timeline-summary";
    this.itemsGraphsElement.id = "timeline-graphs";

    this._createStatusbarButtons();

    this.calculator = new WebInspector.TimelineCalculator();
    for (category in this.categories)
        this.showCategory(category);
    this._sendRequestRecords = {};
}

WebInspector.TimelinePanel.prototype = {
    toolbarItemClass: "timeline",

    get toolbarItemLabel()
    {
        return WebInspector.UIString("Timeline");
    },

    get statusBarItems()
    {
        return [this.toggleTimelineButton.element, this.clearButton.element];
    },

    get categories()
    {
        if (!this._categories) {
            this._categories = {
                loading: new WebInspector.TimelineCategory("loading", WebInspector.UIString("Loading"), "rgb(47,102,236)"),
                scripting: new WebInspector.TimelineCategory("scripting", WebInspector.UIString("Scripting"), "rgb(157,231,119)"),
                rendering: new WebInspector.TimelineCategory("rendering", WebInspector.UIString("Rendering"), "rgb(164,60,255)")
            };
        }
        return this._categories;
    },

    populateSidebar: function()
    {
        this.itemsTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("RECORDS"), {}, true);
        this.itemsTreeElement.expanded = true;
        this.sidebarTree.appendChild(this.itemsTreeElement);
    },

    _createStatusbarButtons: function()
    {
        this.toggleTimelineButton = new WebInspector.StatusBarButton("", "record-profile-status-bar-item");
        this.toggleTimelineButton.addEventListener("click", this._toggleTimelineButtonClicked.bind(this), false);

        this.clearButton = new WebInspector.StatusBarButton("", "timeline-clear-status-bar-item");
        this.clearButton.addEventListener("click", this.reset.bind(this), false);
    },

    timelineWasStarted: function()
    {
        this.toggleTimelineButton.toggled = true;
    },

    timelineWasStopped: function()
    {
        this.toggleTimelineButton.toggled = false;
    },

    addRecordToTimeline: function(record)
    {
        var formattedRecord = this._formatRecord(record);
        // Glue subsequent records with same category and title together if they are closer than 100ms to each other.
        if (this._lastRecord && (!record.children || !record.children.length) &&
                this._lastRecord.category == formattedRecord.category &&
                this._lastRecord.title == formattedRecord.title &&
                this._lastRecord.details == formattedRecord.details &&
                formattedRecord.startTime - this._lastRecord.endTime < 0.1) {
            this._lastRecord.endTime = formattedRecord.endTime;
            this._lastRecord.count++;
            this.refreshItem(this._lastRecord);
        } else {
            this.addItem(formattedRecord);

            for (var i = 0; record.children && i < record.children.length; ++i)
                this.addRecordToTimeline(record.children[i]);
            this._lastRecord = record.children && record.children.length ? null : formattedRecord;
        }
    },

    createItemTreeElement: function(item)
    {
        return new WebInspector.TimelineRecordTreeElement(item);
    },

    createItemGraph: function(item)
    {
        return new WebInspector.TimelineGraph(item);
    },

    _toggleTimelineButtonClicked: function()
    {
        if (this.toggleTimelineButton.toggled)
            InspectorController.stopTimelineProfiler();
        else
            InspectorController.startTimelineProfiler();
    },

    _formatRecord: function(record)
    {
        var recordTypes = WebInspector.TimelineAgent.RecordType;
        if (!this._recordStyles) {
            this._recordStyles = {};
            this._recordStyles[recordTypes.EventDispatch] = { title: WebInspector.UIString("Event"), category: this.categories.scripting };
            this._recordStyles[recordTypes.Layout] = { title: WebInspector.UIString("Layout"), category: this.categories.rendering };
            this._recordStyles[recordTypes.RecalculateStyles] = { title: WebInspector.UIString("Recalculate Style"), category: this.categories.rendering };
            this._recordStyles[recordTypes.Paint] = { title: WebInspector.UIString("Paint"), category: this.categories.rendering };
            this._recordStyles[recordTypes.ParseHTML] = { title: WebInspector.UIString("Parse"), category: this.categories.loading };
            this._recordStyles[recordTypes.TimerInstall] = { title: WebInspector.UIString("Install Timer"), category: this.categories.scripting };
            this._recordStyles[recordTypes.TimerRemove] = { title: WebInspector.UIString("Remove Timer"), category: this.categories.scripting };
            this._recordStyles[recordTypes.TimerFire] = { title: WebInspector.UIString("Timer Fired"), category: this.categories.scripting };
            this._recordStyles[recordTypes.XHRReadyStateChange] = { title: WebInspector.UIString("XHR Ready State Change"), category: this.categories.scripting };
            this._recordStyles[recordTypes.XHRLoad] = { title: WebInspector.UIString("XHR Load"), category: this.categories.scripting };
            this._recordStyles[recordTypes.EvaluateScript] = { title: WebInspector.UIString("Evaluate Script"), category: this.categories.scripting };
            this._recordStyles[recordTypes.MarkTimeline] = { title: WebInspector.UIString("Mark"), category: this.categories.scripting };
            this._recordStyles[recordTypes.ResourceSendRequest] = { title: WebInspector.UIString("Send Request"), category: this.categories.loading };
            this._recordStyles[recordTypes.ResourceReceiveResponse] = { title: WebInspector.UIString("Receive Response"), category: this.categories.loading };
            this._recordStyles[recordTypes.ResourceFinish] = { title: WebInspector.UIString("Finish Loading"), category: this.categories.loading };
        }

        var style = this._recordStyles[record.type];
        if (!style)
            style = this._recordStyles[recordTypes.EventDispatch];

        var formattedRecord = {};
        formattedRecord.category = style.category;
        formattedRecord.title = style.title;
        formattedRecord.startTime = record.startTime / 1000;
        formattedRecord.data = record.data;
        formattedRecord.count = 1;
        formattedRecord.type = record.type;
        formattedRecord.endTime = (typeof record.endTime !== "undefined") ? record.endTime / 1000 : formattedRecord.startTime;

        // Make resource receive record last since request was sent; make finish record last since response received.
        if (record.type === WebInspector.TimelineAgent.RecordType.ResourceSendRequest) {
            this._sendRequestRecords[record.data.identifier] = formattedRecord;
        } else if (record.type === WebInspector.TimelineAgent.RecordType.ResourceReceiveResponse) {
            var sendRequestRecord = this._sendRequestRecords[record.data.identifier];
            sendRequestRecord._responseReceivedFormattedTime = formattedRecord.startTime;
            formattedRecord.startTime = sendRequestRecord.startTime;
            sendRequestRecord.details = this._getRecordDetails(record);
            this.refreshItem(sendRequestRecord);
        } else if (record.type === WebInspector.TimelineAgent.RecordType.ResourceFinish) {
            var sendRequestRecord = this._sendRequestRecords[record.data.identifier];
            if (sendRequestRecord) // False for main resource.
                formattedRecord.startTime = sendRequestRecord._responseReceivedFormattedTime;
        }
        formattedRecord.details = this._getRecordDetails(record);

        return formattedRecord;
    },

    _getRecordDetails: function(record)
    {
        switch (record.type) {
        case WebInspector.TimelineAgent.RecordType.EventDispatch:
            return record.data ? record.data.type : "";
        case WebInspector.TimelineAgent.RecordType.Paint:
            return record.data.width + "\u2009\u00d7\u2009" + record.data.height;
        case WebInspector.TimelineAgent.RecordType.TimerInstall:
        case WebInspector.TimelineAgent.RecordType.TimerRemove:
        case WebInspector.TimelineAgent.RecordType.TimerFire:
            return record.data.timerId;
        case WebInspector.TimelineAgent.RecordType.XHRReadyStateChange:
        case WebInspector.TimelineAgent.RecordType.XHRLoad:
        case WebInspector.TimelineAgent.RecordType.EvaluateScript:
        case WebInspector.TimelineAgent.RecordType.ResourceSendRequest:
            return WebInspector.displayNameForURL(record.data.url);
        case WebInspector.TimelineAgent.RecordType.ResourceReceiveResponse:
        case WebInspector.TimelineAgent.RecordType.ResourceFinish:
            var sendRequestRecord = this._sendRequestRecords[record.data.identifier];
            return sendRequestRecord ? WebInspector.displayNameForURL(sendRequestRecord.data.url) : "";
        case WebInspector.TimelineAgent.RecordType.MarkTimeline:
            return record.data.message;
        default:
            return "";
        }
    },

    reset: function()
    {
        WebInspector.AbstractTimelinePanel.prototype.reset.call(this);
        this._lastRecord = null;
        this._overviewCalculator.reset();
        for (var category in this.categories)
            this._categoryGraphs[category].clearChunks();
        this._setWindowPosition(0, this._overviewGridElement.clientWidth);
        this._sendRequestRecords = {};
    },

    _createOverview: function()
    {
        var overviewPanelElement = document.createElement("div");
        overviewPanelElement.id = "timeline-overview-panel";
        this.element.appendChild(overviewPanelElement);

        this._overviewSidebarElement = document.createElement("div");
        this._overviewSidebarElement.id = "timeline-overview-sidebar";
        overviewPanelElement.appendChild(this._overviewSidebarElement);

        var overviewTreeElement = document.createElement("ol");
        overviewTreeElement.className = "sidebar-tree";
        this._overviewSidebarElement.appendChild(overviewTreeElement);
        var sidebarTree = new TreeOutline(overviewTreeElement);

        var categoriesTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("TIMELINES"), {}, true);
        categoriesTreeElement.expanded = true;
        sidebarTree.appendChild(categoriesTreeElement);

        for (var category in this.categories)
            categoriesTreeElement.appendChild(new WebInspector.TimelineCategoryTreeElement(this.categories[category]));

        this._overviewGridElement = document.createElement("div");
        this._overviewGridElement.id = "timeline-overview-grid";
        overviewPanelElement.appendChild(this._overviewGridElement);
        this._overviewGrid = new WebInspector.TimelineGrid(this._overviewGridElement);
        this._overviewGrid.itemsGraphsElement.id = "timeline-overview-graphs";

        this._categoryGraphs = {};
        for (var category in this.categories) {
            var categoryGraph = new WebInspector.TimelineCategoryGraph(this.categories[category]);
            this._categoryGraphs[category] = categoryGraph;
            this._overviewGrid.itemsGraphsElement.appendChild(categoryGraph.graphElement);
        }
        this._overviewGrid.setScrollAndDividerTop(0, 0);

        this._overviewWindowElement = document.createElement("div");
        this._overviewWindowElement.id = "timeline-overview-window";
        this._overviewWindowElement.addEventListener("mousedown", this._dragWindow.bind(this), false);
        this._overviewGridElement.appendChild(this._overviewWindowElement);

        this._leftResizeElement = document.createElement("div");
        this._leftResizeElement.className = "timeline-window-resizer";
        this._leftResizeElement.style.left = 0;
        this._overviewGridElement.appendChild(this._leftResizeElement);
        this._leftResizeElement.addEventListener("mousedown", this._resizeWindow.bind(this, this._leftResizeElement), false);

        this._rightResizeElement = document.createElement("div");
        this._rightResizeElement.className = "timeline-window-resizer timeline-window-resizer-right";
        this._rightResizeElement.style.right = 0;
        this._overviewGridElement.appendChild(this._rightResizeElement);
        this._rightResizeElement.addEventListener("mousedown", this._resizeWindow.bind(this, this._rightResizeElement), false);

        this._overviewCalculator = new WebInspector.TimelineCalculator();

        var separatorElement = document.createElement("div");
        separatorElement.id = "timeline-overview-separator";
        this.element.appendChild(separatorElement);
    },

    setSidebarWidth: function(width)
    {
        WebInspector.AbstractTimelinePanel.prototype.setSidebarWidth.call(this, width);
        this._overviewSidebarElement.style.width = width + "px";
    },

    updateMainViewWidth: function(width)
    {
        WebInspector.AbstractTimelinePanel.prototype.updateMainViewWidth.call(this, width);
        this._overviewGridElement.style.left = width + "px";
    },

    updateGraphDividersIfNeeded: function()
    {
        WebInspector.AbstractTimelinePanel.prototype.updateGraphDividersIfNeeded.call(this);
        this._overviewGrid.updateDividers(true, this._overviewCalculator);
    },

    refresh: function()
    {
        WebInspector.AbstractTimelinePanel.prototype.refresh.call(this);
        this.adjustScrollPosition();

        // Clear summary bars.
        var timelines = {};
        for (var category in this.categories) {
            timelines[category] = [];
            this._categoryGraphs[category].clearChunks();
        }

        // Create sparse arrays with 101 cells each to fill with chunks for a given category.
        for (var i = 0; i < this.items.length; ++i) {
            var record = this.items[i];
            this._overviewCalculator.updateBoundaries(record);
            var percentages = this._overviewCalculator.computeBarGraphPercentages(record);
            var end = Math.round(percentages.end);
            var categoryName = record.category.name;
            for (var j = Math.round(percentages.start); j <= end; ++j)
                timelines[categoryName][j] = true;
        }

        // Convert sparse arrays to continuous segments, render graphs for each.
        for (var category in this.categories) {
            var timeline = timelines[category];
            window.timelineSaved = timeline;
            var chunkStart = -1;
            for (var j = 0; j < 101; ++j) {
                if (timeline[j]) {
                    if (chunkStart === -1)
                        chunkStart = j;
                } else {
                    if (chunkStart !== -1) {
                        this._categoryGraphs[category].addChunk(chunkStart, j);
                        chunkStart = -1;
                    }
                }
            }
            if (chunkStart !== -1) {
                this._categoryGraphs[category].addChunk(chunkStart, 100);
                chunkStart = -1;
            }
        }
        this._overviewGrid.updateDividers(true, this._overviewCalculator);
    },

    _resizeWindow: function(resizeElement, event)
    {
        WebInspector.elementDragStart(resizeElement, this._windowResizeDragging.bind(this, resizeElement), this._endWindowDragging.bind(this), event, "col-resize");
    },

    _windowResizeDragging: function(resizeElement, event)
    {
        if (resizeElement === this._leftResizeElement)
            this._resizeWindowLeft(event.pageX - this._overviewGridElement.offsetLeft);
        else
            this._resizeWindowRight(event.pageX - this._overviewGridElement.offsetLeft);
        event.preventDefault();
    },

    _dragWindow: function(event)
    {
        WebInspector.elementDragStart(this._overviewWindowElement, this._windowDragging.bind(this, event.pageX,
            this._leftResizeElement.offsetLeft, this._rightResizeElement.offsetLeft), this._endWindowDragging.bind(this), event, "ew-resize");
    },

    _windowDragging: function(startX, windowLeft, windowRight, event)
    {
        var delta = event.pageX - startX;
        var start = windowLeft + delta;
        var end = windowRight + delta;
        var windowSize = windowRight - windowLeft;

        if (start < 0) {
            start = 0;
            end = windowSize;
        }

        if (end > this._overviewGridElement.clientWidth) {
            end = this._overviewGridElement.clientWidth;
            start = end - windowSize;
        }
        this._setWindowPosition(start, end);

        event.preventDefault();
    },

    _resizeWindowLeft: function(start)
    {
        // Glue to edge.
        if (start < 10)
            start = 0;
        this._setWindowPosition(start, null);
    },

    _resizeWindowRight: function(end)
    {
        // Glue to edge.
        if (end > this._overviewGridElement.clientWidth - 10)
            end = this._overviewGridElement.clientWidth;
        this._setWindowPosition(null, end);
    },

    _setWindowPosition: function(start, end)
    {
        this.calculator.reset();
        this.invalidateAllItems();
        if (typeof start === "number") {
            if (start > this._rightResizeElement.offsetLeft - 25)
                start = this._rightResizeElement.offsetLeft - 25;

            var windowLeft = start / this._overviewGridElement.clientWidth;
            this.calculator.windowLeft = windowLeft;
            this._leftResizeElement.style.left = windowLeft * 100 + "%";
            this._overviewWindowElement.style.left = windowLeft * 100 + "%";
        }
        if (typeof end === "number") {
            if (end < this._leftResizeElement.offsetLeft + 30)
                end = this._leftResizeElement.offsetLeft + 30;

            var windowRight = end / this._overviewGridElement.clientWidth;
            this.calculator.windowRight = windowRight;
            this._rightResizeElement.style.left = windowRight * 100 + "%";
        }
        this._overviewWindowElement.style.width = (this.calculator.windowRight - this.calculator.windowLeft) * 100 + "%";
        this.needsRefresh = true;
    },

    _endWindowDragging: function(event)
    {
        WebInspector.elementDragEnd(event);
    }
}

WebInspector.TimelinePanel.prototype.__proto__ = WebInspector.AbstractTimelinePanel.prototype;


WebInspector.TimelineCategory = function(name, title, color)
{
    WebInspector.AbstractTimelineCategory.call(this, name, title, color);
}

WebInspector.TimelineCategory.prototype = {
}

WebInspector.TimelineCategory.prototype.__proto__ = WebInspector.AbstractTimelineCategory.prototype;



WebInspector.TimelineCategoryTreeElement = function(category)
{
    this._category = category;

    // Pass an empty title, the title gets made later in onattach.
    TreeElement.call(this, "", null, false);
}

WebInspector.TimelineCategoryTreeElement.prototype = {
    onattach: function()
    {
        this.listItemElement.removeChildren();
        this.listItemElement.addStyleClass("timeline-category-tree-item");
        this.listItemElement.addStyleClass("timeline-category-" + this._category.name);

        var label = document.createElement("label");

        var checkElement = document.createElement("input");
        checkElement.type = "checkbox";
        checkElement.className = "timeline-category-checkbox";
        checkElement.checked = true;
        checkElement.addEventListener("click", this._onCheckboxClicked.bind(this));
        label.appendChild(checkElement);

        var typeElement = document.createElement("span");
        typeElement.className = "type";
        typeElement.textContent = this._category.title;
        label.appendChild(typeElement);

        this.listItemElement.appendChild(label);
    },

    _onCheckboxClicked: function (event) {
        if (event.target.checked)
            WebInspector.panels.timeline.showCategory(this._category.name);
        else {
            WebInspector.panels.timeline.hideCategory(this._category.name);
            WebInspector.panels.timeline.adjustScrollPosition();
        }
        WebInspector.panels.timeline._categoryGraphs[this._category.name].dimmed = !event.target.checked;
    }
}

WebInspector.TimelineCategoryTreeElement.prototype.__proto__ = TreeElement.prototype;


WebInspector.TimelineRecordTreeElement = function(record)
{
    this._record = record;

    // Pass an empty title, the title gets made later in onattach.
    TreeElement.call(this, "", null, false);
}

WebInspector.TimelineRecordTreeElement.prototype = {
    onattach: function()
    {
        this.listItemElement.removeChildren();
        this.listItemElement.addStyleClass("timeline-tree-item");
        this.listItemElement.addStyleClass("timeline-category-" + this._record.category.name);

        var iconElement = document.createElement("span");
        iconElement.className = "timeline-tree-icon";
        this.listItemElement.appendChild(iconElement);

        this.typeElement = document.createElement("span");
        this.typeElement.className = "type";
        this.typeElement.textContent = this._record.title;
        this.listItemElement.appendChild(this.typeElement);

        if (this._record.details) {
            var separatorElement = document.createElement("span");
            separatorElement.className = "separator";
            separatorElement.textContent = " ";

            this.dataElement = document.createElement("span");
            this.dataElement.className = "data dimmed";
            this._updateDetails();

            this.listItemElement.appendChild(separatorElement);
            this.listItemElement.appendChild(this.dataElement);
        }
    },

    _updateDetails: function()
    {
        if (this.dataElement && this._record.details !== this._details) {
            this._details = this._record.details;
            this.dataElement.textContent = "(" + this._details + ")";
            this.dataElement.title = this._details;
        }
    },

    refresh: function()
    {
        this._updateDetails();

        if (this._record.count <= 1)
            return;

        if (!this.repeatCountElement) {
            this.repeatCountElement = document.createElement("span");
            this.repeatCountElement.className = "count";
            this.listItemElement.appendChild(this.repeatCountElement);
        }

        this.repeatCountElement.textContent = "\u2009\u00d7\u2009" + this._record.count;
    }
}

WebInspector.TimelineRecordTreeElement.prototype.__proto__ = TreeElement.prototype;


WebInspector.TimelineCalculator = function()
{
    WebInspector.AbstractTimelineCalculator.call(this);
    this.windowLeft = 0.0;
    this.windowRight = 1.0;
    this._uiString = WebInspector.UIString.bind(WebInspector);
}

WebInspector.TimelineCalculator.prototype = {
    computeBarGraphPercentages: function(record)
    {
        var start = (record.startTime - this.minimumBoundary) / this.boundarySpan * 100;
        var end = (record.endTime - this.minimumBoundary) / this.boundarySpan * 100;
        return {start: start, end: end};
    },

    computePercentageFromEventTime: function(eventTime)
    {
        return ((eventTime - this.minimumBoundary) / this.boundarySpan) * 100;
    },

    computeBarGraphLabels: function(record)
    {
        return {tooltip: record.title};
    },

    get minimumBoundary()
    {
        if (typeof this._minimumBoundary === "number")
            return this._minimumBoundary;

        if (typeof this.windowLeft === "number")
            this._minimumBoundary = this._absoluteMinimumBoundary + this.windowLeft * (this._absoluteMaximumBoundary - this._absoluteMinimumBoundary);
        else
            this._minimumBoundary = this._absoluteMinimumBoundary;
        return this._minimumBoundary;
    },

    get maximumBoundary()
    {
        if (typeof this._maximumBoundary === "number")
            return this._maximumBoundary;

        if (typeof this.windowLeft === "number")
            this._maximumBoundary = this._absoluteMinimumBoundary + this.windowRight * (this._absoluteMaximumBoundary - this._absoluteMinimumBoundary);
        else
            this._maximumBoundary = this._absoluteMaximumBoundary;
        return this._maximumBoundary;
    },

    reset: function()
    {
        delete this._absoluteMinimumBoundary;
        delete this._absoluteMaximumBoundary;
        delete this._minimumBoundary;
        delete this._maximumBoundary;
    },

    updateBoundaries: function(record)
    {
        var didChange = false;

        var lowerBound = record.startTime;

        if (typeof this._absoluteMinimumBoundary === "undefined" || lowerBound < this._absoluteMinimumBoundary) {
            this._absoluteMinimumBoundary = lowerBound;
            delete this._minimumBoundary;
            didChange = true;
        }

        var upperBound = record.endTime;
        if (typeof this._absoluteMaximumBoundary === "undefined" || upperBound > this._absoluteMaximumBoundary) {
            this._absoluteMaximumBoundary = upperBound;
            delete this._maximumBoundary;
            didChange = true;
        }

        return didChange;
    },

    formatValue: function(value)
    {
        return Number.secondsToString(value + this.minimumBoundary - this._absoluteMinimumBoundary, this._uiString);
    }
}

WebInspector.TimelineCalculator.prototype.__proto__ = WebInspector.AbstractTimelineCalculator.prototype;


WebInspector.TimelineCategoryGraph = function(category)
{
    this._category = category;

    this._graphElement = document.createElement("div");
    this._graphElement.className = "timeline-graph-side timeline-overview-graph-side";

    this._barAreaElement = document.createElement("div");
    this._barAreaElement.className = "timeline-graph-bar-area timeline-category-" + category.name;
    this._graphElement.appendChild(this._barAreaElement);
}

WebInspector.TimelineCategoryGraph.prototype = {
    get graphElement()
    {
        return this._graphElement;
    },

    addChunk: function(start, end)
    {
        var chunk = document.createElement("div");
        chunk.className = "timeline-graph-bar";
        this._barAreaElement.appendChild(chunk);
        chunk.style.setProperty("left", start + "%");
        chunk.style.setProperty("width", (end - start) + "%");
    },

    clearChunks: function()
    {
        this._barAreaElement.removeChildren();
    },

    set dimmed(dimmed)
    {
        if (dimmed)
            this._barAreaElement.removeStyleClass("timeline-category-" + this._category.name);
        else
            this._barAreaElement.addStyleClass("timeline-category-" + this._category.name);
    }
}


WebInspector.TimelineGraph = function(record)
{
    this.record = record;

    this._graphElement = document.createElement("div");
    this._graphElement.className = "timeline-graph-side";

    this._barAreaElement = document.createElement("div");
    this._barAreaElement.className = "timeline-graph-bar-area";
    this._graphElement.appendChild(this._barAreaElement);

    this._barElement = document.createElement("div");
    this._barElement.className = "timeline-graph-bar";
    this._barAreaElement.appendChild(this._barElement);

    this._graphElement.addStyleClass("timeline-category-" + record.category.name);
    this._hidden = false;
}

WebInspector.TimelineGraph.prototype = {
    get graphElement()
    {
        return this._graphElement;
    },

    refreshLabelPositions: function()
    {
    },

    refresh: function(calculator)
    {
        var percentages = calculator.computeBarGraphPercentages(this.record);
        var labels = calculator.computeBarGraphLabels(this.record);

        this._percentages = percentages;

        if (percentages.start > 100 || percentages.end < 0) {
            if (!this._hidden) {
                this._graphElement.addStyleClass("hidden");
                this.record._itemsTreeElement.listItemElement.addStyleClass("hidden");
                this._hidden = true;
            }
        } else {
            this._barElement.style.setProperty("left", percentages.start + "%");
            this._barElement.style.setProperty("right", (100 - percentages.end) + "%");
            if (this._hidden) {
                this._graphElement.removeStyleClass("hidden");
                this.record._itemsTreeElement.listItemElement.removeStyleClass("hidden");
                this._hidden = false;
            }
        }
        var tooltip = (labels.tooltip || "");
        this._barElement.title = tooltip;
    }
}

/* TestController.js */

/*
 * Copyright (C) 2009 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.TestController = function(callId)
{
    this._callId = callId;
    this._waitUntilDone = false;
}

WebInspector.TestController.prototype = {
    waitUntilDone: function()
    {
        this._waitUntilDone = true;
    },

    notifyDone: function(result)
    {
        var message = typeof result === "undefined" ? "\"<undefined>\"" : JSON.stringify(result);
        InspectorController.didEvaluateForTestInFrontend(this._callId, message);
    },

    runAfterPendingDispatches: function(callback)
    {
        if (WebInspector.pendingDispatches === 0) {
            callback();
            return;
        }
        setTimeout(this.runAfterPendingDispatches.bind(this), 0, callback);
    }
}

WebInspector.evaluateForTestInFrontend = function(callId, script)
{
    var controller = new WebInspector.TestController(callId);
    function invokeMethod()
    {
        try {
            var result;
            if (window[script] && typeof window[script] === "function")
                result = window[script].call(WebInspector, controller);
            else
                result = window.eval(script);

            if (!controller._waitUntilDone)
                controller.notifyDone(result);
        } catch (e) {
            controller.notifyDone(e.toString());
        }
    }
    controller.runAfterPendingDispatches(invokeMethod);
}

/* base.js */

// Copyright 2006 Google Inc.
// All Rights Reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
//  * Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
//  * Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in
//    the documentation and/or other materials provided with the
//    distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
// COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

// NOTE: This file has been changed from the one on doctype. The following
// changes were made:
// - Removed goog.globalEval because it calls eval() which is not allowed from
//   inside v8 extensions. If we ever need to use globalEval, we will need to
//   find a way to work around this problem.
// - Remove Function.prototype.apply() emulation for the same reason. This one
//   is useless anyway because V8 supports apply() natively.

/**
 * @fileoverview Bootstrap for the Google JS Library
 */

/**
 * @define {boolean} Overridden to true by the compiler when
 *     --mark_as_compiled is specified.
 */
var COMPILED = true;


/**
 * Base namespace for the Google JS library.  Checks to see goog is
 * already defined in the current scope before assigning to prevent
 * clobbering if base.js is loaded more than once.
 */
var goog = {}; // Check to see if already defined in current scope


/**
 * Reference to the global context.  In most cases this will be 'window'.
 */
goog.global = this;


/**
 * Indicates whether or not we can call 'eval' directly to eval code in the
 * global scope. Set to a Boolean by the first call to goog.globalEval (which
 * empirically tests whether eval works for globals). @see goog.globalEval
 * @type {boolean?}
 * @private
 */
goog.evalWorksForGlobals_ = null;


/**
 * Creates object stubs for a namespace. When present in a file, goog.provide
 * also indicates that the file defines the indicated object.
 * @param {string} name name of the object that this file defines.
 */
goog.provide = function(name) {
  if (!COMPILED) {
    // Ensure that the same namespace isn't provided twice. This is intended
    // to teach new developers that 'goog.provide' is effectively a variable
    // declaration. And when JSCompiler transforms goog.provide into a real
    // variable declaration, the compiled JS should work the same as the raw
    // JS--even when the raw JS uses goog.provide incorrectly.
    if (goog.getObjectByName(name) && !goog.implicitNamespaces_[name]) {
      throw 'Namespace "' + name + '" already declared.';
    }

    var namespace = name;
    while ((namespace = namespace.substr(0, namespace.lastIndexOf('.')))) {
      goog.implicitNamespaces_[namespace] = true;
    }
  }

  goog.exportPath_(name);
};


if (!COMPILED) {
  /**
   * Namespaces implicitly defined by goog.provide. For example,
   * goog.provide('goog.events.Event') implicitly declares
   * that 'goog' and 'goog.events' must be namespaces.
   *
   * @type {Object}
   * @private
   */
  goog.implicitNamespaces_ = {};
}


/**
 * Builds an object structure for the provided namespace path,
 * ensuring that names that already exist are not overwritten. For
 * example:
 * "a.b.c" -> a = {};a.b={};a.b.c={};
 * Used by goog.provide and goog.exportSymbol.
 * @param {string} name name of the object that this file defines.
 * @param {Object} opt_object the object to expose at the end of the path.
 * @private
 */
goog.exportPath_ = function(name, opt_object) {
  var parts = name.split('.');
  var cur = goog.global;
  var part;

  // Internet Explorer exhibits strange behavior when throwing errors from
  // methods externed in this manner.  See the testExportSymbolExceptions in
  // base_test.html for an example.
  if (!(parts[0] in cur) && cur.execScript) {
    cur.execScript('var ' + parts[0]);
  }

  // Parentheses added to eliminate strict JS warning in Firefox.
  while ((part = parts.shift())) {
    if (!parts.length && goog.isDef(opt_object)) {
      // last part and we have an object; use it
      cur[part] = opt_object;
    } else if (cur[part]) {
      cur = cur[part];
    } else {
      cur = cur[part] = {};
    }
  }
};


/**
 * Returns an object based on its fully qualified name
 * @param {string} name The fully qualified name.
 * @return {Object?} The object or, if not found, null.
 */
goog.getObjectByName = function(name) {
  var parts = name.split('.');
  var cur = goog.global;
  for (var part; part = parts.shift(); ) {
    if (cur[part]) {
      cur = cur[part];
    } else {
      return null;
    }
  }
  return cur;
};


/**
 * Globalizes a whole namespace, such as goog or goog.lang.
 *
 * @param {Object} obj The namespace to globalize.
 * @param {Object} opt_global The object to add the properties to.
 * @deprecated Properties may be explicitly exported to the global scope, but
 *     this should no longer be done in bulk.
 */
goog.globalize = function(obj, opt_global) {
  var global = opt_global || goog.global;
  for (var x in obj) {
    global[x] = obj[x];
  }
};


/**
 * Adds a dependency from a file to the files it requires.
 * @param {string} relPath The path to the js file.
 * @param {Array} provides An array of strings with the names of the objects
 *                         this file provides.
 * @param {Array} requires An array of strings with the names of the objects
 *                         this file requires.
 */
goog.addDependency = function(relPath, provides, requires) {
  if (!COMPILED) {
    var provide, require;
    var path = relPath.replace(/\\/g, '/');
    var deps = goog.dependencies_;
    for (var i = 0; provide = provides[i]; i++) {
      deps.nameToPath[provide] = path;
      if (!(path in deps.pathToNames)) {
        deps.pathToNames[path] = {};
      }
      deps.pathToNames[path][provide] = true;
    }
    for (var j = 0; require = requires[j]; j++) {
      if (!(path in deps.requires)) {
        deps.requires[path] = {};
      }
      deps.requires[path][require] = true;
    }
  }
};


/**
 * Implements a system for the dynamic resolution of dependencies
 * that works in parallel with the BUILD system.
 * @param {string} rule Rule to include, in the form goog.package.part.
 */
goog.require = function(rule) {

  // if the object already exists we do not need do do anything
  if (!COMPILED) {
    if (goog.getObjectByName(rule)) {
      return;
    }
    var path = goog.getPathFromDeps_(rule);
    if (path) {
      goog.included_[path] = true;
      goog.writeScripts_();
    } else {
      // NOTE(nicksantos): We could throw an error, but this would break
      // legacy users that depended on this failing silently. Instead, the
      // compiler should warn us when there are invalid goog.require calls.
    }
  }
};


/**
 * Path for included scripts
 * @type {string}
 */
goog.basePath = '';


/**
 * Null function used for default values of callbacks, etc.
 * @type {Function}
 */
goog.nullFunction = function() {};


/**
 * When defining a class Foo with an abstract method bar(), you can do:
 *
 * Foo.prototype.bar = goog.abstractMethod
 *
 * Now if a subclass of Foo fails to override bar(), an error
 * will be thrown when bar() is invoked.
 *
 * Note: This does not take the name of the function to override as
 * an argument because that would make it more difficult to obfuscate
 * our JavaScript code.
 *
 * @throws {Error} when invoked to indicate the method should be
 *   overridden.
 */
goog.abstractMethod = function() {
  throw Error('unimplemented abstract method');
};


if (!COMPILED) {
  /**
   * Object used to keep track of urls that have already been added. This
   * record allows the prevention of circular dependencies.
   * @type {Object}
   * @private
   */
  goog.included_ = {};


  /**
   * This object is used to keep track of dependencies and other data that is
   * used for loading scripts
   * @private
   * @type {Object}
   */
  goog.dependencies_ = {
    pathToNames: {}, // 1 to many
    nameToPath: {}, // 1 to 1
    requires: {}, // 1 to many
    visited: {}, // used when resolving dependencies to prevent us from
                 // visiting the file twice
    written: {} // used to keep track of script files we have written
  };


  /**
   * Tries to detect the base path of the base.js script that bootstraps
   * Google JS Library
   * @private
   */
  goog.findBasePath_ = function() {
    var doc = goog.global.document;
    if (typeof doc == 'undefined') {
      return;
    }
    if (goog.global.GOOG_BASE_PATH) {
      goog.basePath = goog.global.GOOG_BASE_PATH;
      return;
    } else {
      goog.global.GOOG_BASE_PATH = null;
    }
    var scripts = doc.getElementsByTagName('script');
    for (var script, i = 0; script = scripts[i]; i++) {
      var src = script.src;
      var l = src.length;
      if (src.substr(l - 7) == 'base.js') {
        goog.basePath = src.substr(0, l - 7);
        return;
      }
    }
  };


  /**
   * Writes a script tag if, and only if, that script hasn't already been added
   * to the document.  (Must be called at execution time)
   * @param {string} src Script source.
   * @private
   */
  goog.writeScriptTag_ = function(src) {
    var doc = goog.global.document;
    if (typeof doc != 'undefined' &&
        !goog.dependencies_.written[src]) {
      goog.dependencies_.written[src] = true;
      doc.write('<script type="text/javascript" src="' +
                src + '"></' + 'script>');
    }
  };


  /**
   * Resolves dependencies based on the dependencies added using addDependency
   * and calls writeScriptTag_ in the correct order.
   * @private
   */
  goog.writeScripts_ = function() {
    // the scripts we need to write this time
    var scripts = [];
    var seenScript = {};
    var deps = goog.dependencies_;

    function visitNode(path) {
      if (path in deps.written) {
        return;
      }

      // we have already visited this one. We can get here if we have cyclic
      // dependencies
      if (path in deps.visited) {
        if (!(path in seenScript)) {
          seenScript[path] = true;
          scripts.push(path);
        }
        return;
      }

      deps.visited[path] = true;

      if (path in deps.requires) {
        for (var requireName in deps.requires[path]) {
          visitNode(deps.nameToPath[requireName]);
        }
      }

      if (!(path in seenScript)) {
        seenScript[path] = true;
        scripts.push(path);
      }
    }

    for (var path in goog.included_) {
      if (!deps.written[path]) {
        visitNode(path);
      }
    }

    for (var i = 0; i < scripts.length; i++) {
      if (scripts[i]) {
        goog.writeScriptTag_(goog.basePath + scripts[i]);
      } else {
        throw Error('Undefined script input');
      }
    }
  };


  /**
   * Looks at the dependency rules and tries to determine the script file that
   * fulfills a particular rule.
   * @param {string} rule In the form goog.namespace.Class or project.script.
   * @return {string?} Url corresponding to the rule, or null.
   * @private
   */
  goog.getPathFromDeps_ = function(rule) {
    if (rule in goog.dependencies_.nameToPath) {
      return goog.dependencies_.nameToPath[rule];
    } else {
      return null;
    }
  };

  goog.findBasePath_();
  goog.writeScriptTag_(goog.basePath + 'deps.js');
}



//==============================================================================
// Language Enhancements
//==============================================================================


/**
 * This is a "fixed" version of the typeof operator.  It differs from the typeof
 * operator in such a way that null returns 'null' and arrays return 'array'.
 * @param {*} value The value to get the type of.
 * @return {string} The name of the type.
 */
goog.typeOf = function(value) {
  var s = typeof value;
  if (s == 'object') {
    if (value) {
      // We cannot use constructor == Array or instanceof Array because
      // different frames have different Array objects. In IE6, if the iframe
      // where the array was created is destroyed, the array loses its
      // prototype. Then dereferencing val.splice here throws an exception, so
      // we can't use goog.isFunction. Calling typeof directly returns 'unknown'
      // so that will work. In this case, this function will return false and
      // most array functions will still work because the array is still
      // array-like (supports length and []) even though it has lost its
      // prototype. Custom object cannot have non enumerable length and
      // NodeLists don't have a slice method.
      if (typeof value.length == 'number' &&
          typeof value.splice != 'undefined' &&
          !goog.propertyIsEnumerable_(value, 'length')) {
        return 'array';
      }

      // IE in cross-window calls does not correctly marshal the function type
      // (it appears just as an object) so we cannot use just typeof val ==
      // 'function'. However, if the object has a call property, it is a
      // function.
      if (typeof value.call != 'undefined') {
        return 'function';
      }
    } else {
      return 'null';
    }

  // In Safari typeof nodeList returns function.  We would like to return
  // object for those and we can detect an invalid function by making sure that
  // the function object has a call method
  } else if (s == 'function' && typeof value.call == 'undefined') {
    return 'object';
  }
  return s;
};

if (Object.prototype.propertyIsEnumerable) {
  /**
   * Safe way to test whether a property is enumarable.  It allows testing
   * for enumarable on objects where 'propertyIsEnumerable' is overridden or
   * does not exist (like DOM nodes in IE).
   * @param {Object} object The object to test if the property is enumerable.
   * @param {string} propName The property name to check for.
   * @return {boolean} True if the property is enumarable.
   * @private
   */
  goog.propertyIsEnumerable_ = function(object, propName) {
    return Object.prototype.propertyIsEnumerable.call(object, propName);
  };
} else {
  /**
   * Safe way to test whether a property is enumarable.  It allows testing
   * for enumarable on objects where 'propertyIsEnumerable' is overridden or
   * does not exist (like DOM nodes in IE).
   * @param {Object} object The object to test if the property is enumerable.
   * @param {string} propName The property name to check for.
   * @return {boolean} True if the property is enumarable.
   * @private
   */
  goog.propertyIsEnumerable_ = function(object, propName) {
    // KJS in Safari 2 is not ECMAScript compatible and lacks crucial methods
    // such as propertyIsEnumerable.  We therefore use a workaround.
    // Does anyone know a more efficient work around?
    if (propName in object) {
      for (var key in object) {
        if (key == propName) {
          return true;
        }
      }
    }
    return false;
  };
}

/**
 * Returns true if the specified value is not |undefined|.
 * WARNING: Do not use this to test if an object has a property. Use the in
 * operator instead.
 * @param {*} val Variable to test.
 * @return {boolean} Whether variable is defined.
 */
goog.isDef = function(val) {
  return typeof val != 'undefined';
};


/**
 * Returns true if the specified value is |null|
 * @param {*} val Variable to test.
 * @return {boolean} Whether variable is null.
 */
goog.isNull = function(val) {
  return val === null;
};


/**
 * Returns true if the specified value is defined and not null
 * @param {*} val Variable to test.
 * @return {boolean} Whether variable is defined and not null.
 */
goog.isDefAndNotNull = function(val) {
  return goog.isDef(val) && !goog.isNull(val);
};


/**
 * Returns true if the specified value is an array
 * @param {*} val Variable to test.
 * @return {boolean} Whether variable is an array.
 */
goog.isArray = function(val) {
  return goog.typeOf(val) == 'array';
};


/**
 * Returns true if the object looks like an array. To qualify as array like
 * the value needs to be either a NodeList or an object with a Number length
 * property.
 * @param {*} val Variable to test.
 * @return {boolean} Whether variable is an array.
 */
goog.isArrayLike = function(val) {
  var type = goog.typeOf(val);
  return type == 'array' || type == 'object' && typeof val.length == 'number';
};


/**
 * Returns true if the object looks like a Date. To qualify as Date-like
 * the value needs to be an object and have a getFullYear() function.
 * @param {*} val Variable to test.
 * @return {boolean} Whether variable is a like a Date.
 */
goog.isDateLike = function(val) {
  return goog.isObject(val) && typeof val.getFullYear == 'function';
};


/**
 * Returns true if the specified value is a string
 * @param {*} val Variable to test.
 * @return {boolean} Whether variable is a string.
 */
goog.isString = function(val) {
  return typeof val == 'string';
};


/**
 * Returns true if the specified value is a boolean
 * @param {*} val Variable to test.
 * @return {boolean} Whether variable is boolean.
 */
goog.isBoolean = function(val) {
  return typeof val == 'boolean';
};


/**
 * Returns true if the specified value is a number
 * @param {*} val Variable to test.
 * @return {boolean} Whether variable is a number.
 */
goog.isNumber = function(val) {
  return typeof val == 'number';
};


/**
 * Returns true if the specified value is a function
 * @param {*} val Variable to test.
 * @return {boolean} Whether variable is a function.
 */
goog.isFunction = function(val) {
  return goog.typeOf(val) == 'function';
};


/**
 * Returns true if the specified value is an object.  This includes arrays
 * and functions.
 * @param {*} val Variable to test.
 * @return {boolean} Whether variable is an object.
 */
goog.isObject = function(val) {
  var type = goog.typeOf(val);
  return type == 'object' || type == 'array' || type == 'function';
};


/**
 * Adds a hash code field to an object. The hash code is unique for the
 * given object.
 * @param {Object} obj The object to get the hash code for.
 * @return {number} The hash code for the object.
 */
goog.getHashCode = function(obj) {
  // In IE, DOM nodes do not extend Object so they do not have this method.
  // we need to check hasOwnProperty because the proto might have this set.

  if (obj.hasOwnProperty && obj.hasOwnProperty(goog.HASH_CODE_PROPERTY_)) {
    return obj[goog.HASH_CODE_PROPERTY_];
  }
  if (!obj[goog.HASH_CODE_PROPERTY_]) {
    obj[goog.HASH_CODE_PROPERTY_] = ++goog.hashCodeCounter_;
  }
  return obj[goog.HASH_CODE_PROPERTY_];
};


/**
 * Removes the hash code field from an object.
 * @param {Object} obj The object to remove the field from.
 */
goog.removeHashCode = function(obj) {
  // DOM nodes in IE are not instance of Object and throws exception
  // for delete. Instead we try to use removeAttribute
  if ('removeAttribute' in obj) {
    obj.removeAttribute(goog.HASH_CODE_PROPERTY_);
  }
  /** @preserveTry */
  try {
    delete obj[goog.HASH_CODE_PROPERTY_];
  } catch (ex) {
  }
};


/**
 * {String} Name for hash code property
 * @private
 */
goog.HASH_CODE_PROPERTY_ = 'goog_hashCode_';


/**
 * @type {number} Counter for hash codes.
 * @private
 */
goog.hashCodeCounter_ = 0;


/**
 * Clone an object/array (recursively)
 * @param {Object} proto Object to clone.
 * @return {Object} Clone of x;.
 */
goog.cloneObject = function(proto) {
  var type = goog.typeOf(proto);
  if (type == 'object' || type == 'array') {
    if (proto.clone) {
      return proto.clone();
    }
    var clone = type == 'array' ? [] : {};
    for (var key in proto) {
      clone[key] = goog.cloneObject(proto[key]);
    }
    return clone;
  }

  return proto;
};


/**
 * Partially applies this function to a particular 'this object' and zero or
 * more arguments. The result is a new function with some arguments of the first
 * function pre-filled and the value of |this| 'pre-specified'.<br><br>
 *
 * Remaining arguments specified at call-time are appended to the pre-
 * specified ones.<br><br>
 *
 * Also see: {@link #partial}.<br><br>
 *
 * Note that bind and partial are optimized such that repeated calls to it do
 * not create more than one function object, so there is no additional cost for
 * something like:<br>
 *
 * <pre>var g = bind(f, obj);
 * var h = partial(g, 1, 2, 3);
 * var k = partial(h, a, b, c);</pre>
 *
 * Usage:
 * <pre>var barMethBound = bind(myFunction, myObj, 'arg1', 'arg2');
 * barMethBound('arg3', 'arg4');</pre>
 *
 * @param {Function} fn A function to partially apply.
 * @param {Object} self Specifies the object which |this| should point to
 *     when the function is run. If the value is null or undefined, it will
 *     default to the global object.
 * @param {Object} var_args Additional arguments that are partially
 *     applied to the function.
 *
 * @return {Function} A partially-applied form of the function bind() was
 *     invoked as a method of.
 */
goog.bind = function(fn, self, var_args) {
  var boundArgs = fn.boundArgs_;

  if (arguments.length > 2) {
    var args = Array.prototype.slice.call(arguments, 2);
    if (boundArgs) {
      args.unshift.apply(args, boundArgs);
    }
    boundArgs = args;
  }

  self = fn.boundSelf_ || self;
  fn = fn.boundFn_ || fn;

  var newfn;
  var context = self || goog.global;

  if (boundArgs) {
    newfn = function() {
      // Combine the static args and the new args into one big array
      var args = Array.prototype.slice.call(arguments);
      args.unshift.apply(args, boundArgs);
      return fn.apply(context, args);
    }
  } else {
    newfn = function() {
      return fn.apply(context, arguments);
    }
  }

  newfn.boundArgs_ = boundArgs;
  newfn.boundSelf_ = self;
  newfn.boundFn_ = fn;

  return newfn;
};


/**
 * Like bind(), except that a 'this object' is not required. Useful when the
 * target function is already bound.
 *
 * Usage:
 * var g = partial(f, arg1, arg2);
 * g(arg3, arg4);
 *
 * @param {Function} fn A function to partially apply.
 * @param {Object} var_args Additional arguments that are partially
 *     applied to fn.
 * @return {Function} A partially-applied form of the function bind() was
 *     invoked as a method of.
 */
goog.partial = function(fn, var_args) {
  var args = Array.prototype.slice.call(arguments, 1);
  args.unshift(fn, null);
  return goog.bind.apply(null, args);
};


/**
 * Copies all the members of a source object to a target object.
 * This is deprecated. Use goog.object.extend instead.
 * @param {Object} target Target.
 * @param {Object} source Source.
 * @deprecated
 */
goog.mixin = function(target, source) {
  for (var x in source) {
    target[x] = source[x];
  }

  // For IE the for-in-loop does not contain any properties that are not
  // enumerable on the prototype object (for example, isPrototypeOf from
  // Object.prototype) but also it will not include 'replace' on objects that
  // extend String and change 'replace' (not that it is common for anyone to
  // extend anything except Object).
};


/**
 * A simple wrapper for new Date().getTime().
 *
 * @return {number} An integer value representing the number of milliseconds
 *     between midnight, January 1, 1970 and the current time.
 */
goog.now = Date.now || (function() {
  return new Date().getTime();
});


/**
 * Abstract implementation of goog.getMsg for use with localized messages
 * @param {string} str Translatable string, places holders in the form.{$foo}
 * @param {Object} opt_values Map of place holder name to value.
 */
goog.getMsg = function(str, opt_values) {
  var values = opt_values || {};
  for (var key in values) {
    str = str.replace(new RegExp('\\{\\$' + key + '\\}', 'gi'), values[key]);
  }
  return str;
};


/**
 * Exposes an unobfuscated global namespace path for the given object.
 * Note that fields of the exported object *will* be obfuscated,
 * unless they are exported in turn via this function or
 * goog.exportProperty
 *
 * <p>Also handy for making public items that are defined in anonymous
 * closures.
 *
 * ex. goog.exportSymbol('Foo', Foo);
 *
 * ex. goog.exportSymbol('public.path.Foo.staticFunction',
 *                       Foo.staticFunction);
 *     public.path.Foo.staticFunction();
 *
 * ex. goog.exportSymbol('public.path.Foo.prototype.myMethod',
 *                       Foo.prototype.myMethod);
 *     new public.path.Foo().myMethod();
 *
 * @param {string} publicPath Unobfuscated name to export.
 * @param {Object} object Object the name should point to.
 */
goog.exportSymbol = function(publicPath, object) {
  goog.exportPath_(publicPath, object);
};


/**
 * Exports a property unobfuscated into the object's namespace.
 * ex. goog.exportProperty(Foo, 'staticFunction', Foo.staticFunction);
 * ex. goog.exportProperty(Foo.prototype, 'myMethod', Foo.prototype.myMethod);
 * @param {Object} object Object whose static property is being exported.
 * @param {string} publicName Unobfuscated name to export.
 * @param {Object} symbol Object the name should point to.
 */
goog.exportProperty = function(object, publicName, symbol) {
  object[publicName] = symbol;
};



//==============================================================================
// Extending Function
//==============================================================================


/**
 * An alias to the {@link goog.bind()} global function.
 *
 * Usage:
 * var g = f.bind(obj, arg1, arg2);
 * g(arg3, arg4);
 *
 * @param {Object} self Specifies the object to which |this| should point
 *     when the function is run. If the value is null or undefined, it will
 *     default to the global object.
 * @param {Object} var_args Additional arguments that are partially
 *     applied to fn.
 * @return {Function} A partially-applied form of the Function on which bind()
 *     was invoked as a method.
 * @deprecated
 */
Function.prototype.bind = function(self, var_args) {
  if (arguments.length > 1) {
    var args = Array.prototype.slice.call(arguments, 1);
    args.unshift(this, self);
    return goog.bind.apply(null, args);
  } else {
    return goog.bind(this, self);
  }
};


/**
 * An alias to the {@link goog.partial()} global function.
 *
 * Usage:
 * var g = f.partial(arg1, arg2);
 * g(arg3, arg4);
 *
 * @param {Object} var_args Additional arguments that are partially
 *     applied to fn.
 * @return {Function} A partially-applied form of the function partial() was
 *     invoked as a method of.
 * @deprecated
 */
Function.prototype.partial = function(var_args) {
  var args = Array.prototype.slice.call(arguments);
  args.unshift(this, null);
  return goog.bind.apply(null, args);
};


/**
 * Inherit the prototype methods from one constructor into another.
 *
 * Usage:
 * <pre>
 * function ParentClass(a, b) { }
 * ParentClass.prototype.foo = function(a) { }
 *
 * function ChildClass(a, b, c) {
 *   ParentClass.call(this, a, b);
 * }
 *
 * ChildClass.inherits(ParentClass);
 *
 * var child = new ChildClass('a', 'b', 'see');
 * child.foo(); // works
 * </pre>
 *
 * In addition, a superclass' implementation of a method can be invoked
 * as follows:
 *
 * <pre>
 * ChildClass.prototype.foo = function(a) {
 *   ChildClass.superClass_.foo.call(this, a);
 *   // other code
 * };
 * </pre>
 *
 * @param {Function} parentCtor Parent class.
 */
Function.prototype.inherits = function(parentCtor) {
  goog.inherits(this, parentCtor);
};


/**
 * Static variant of Function.prototype.inherits.
 * @param {Function} childCtor Child class.
 * @param {Function} parentCtor Parent class.
 */
goog.inherits = function(childCtor, parentCtor) {
  /** @constructor */
  function tempCtor() {};
  tempCtor.prototype = parentCtor.prototype;
  childCtor.superClass_ = parentCtor.prototype;
  childCtor.prototype = new tempCtor();
  childCtor.prototype.constructor = childCtor;
};


/**
 * Mixes in an object's properties and methods into the callee's prototype.
 * Basically mixin based inheritance, thus providing an alternative method for
 * adding properties and methods to a class' prototype.
 *
 * <pre>
 * function X() {}
 * X.mixin({
 *   one: 1,
 *   two: 2,
 *   three: 3,
 *   doit: function() { return this.one + this.two + this.three; }
 * });
 *
 * function Y() { }
 * Y.mixin(X.prototype);
 * Y.prototype.four = 15;
 * Y.prototype.doit2 = function() { return this.doit() + this.four; }
 * });
 *
 * // or
 *
 * function Y() { }
 * Y.inherits(X);
 * Y.mixin({
 *   one: 10,
 *   four: 15,
 *   doit2: function() { return this.doit() + this.four; }
 * });
 * </pre>
 *
 * @param {Object} source from which to copy properties.
 * @see goog.mixin
 * @deprecated
 */
Function.prototype.mixin = function(source) {
  goog.mixin(this.prototype, source);
};

/* inspector_controller_impl.js */

// Copyright (c) 2009 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/**
 * @fileoverview DevTools' implementation of the InspectorController API.
 */

goog.provide('devtools.InspectorControllerImpl');

devtools.InspectorControllerImpl = function() {
  WebInspector.InspectorControllerStub.call(this);
  this.frame_element_id_ = 1;

  this.installInspectorControllerDelegate_('clearMessages');
  this.installInspectorControllerDelegate_('copyNode');
  this.installInspectorControllerDelegate_('deleteCookie');
  this.installInspectorControllerDelegate_('disableResourceTracking');
  this.installInspectorControllerDelegate_('disableTimeline');
  this.installInspectorControllerDelegate_('enableResourceTracking');
  this.installInspectorControllerDelegate_('enableTimeline');
  this.installInspectorControllerDelegate_('getChildNodes');
  this.installInspectorControllerDelegate_('getCookies');
  this.installInspectorControllerDelegate_('getDatabaseTableNames');
  this.installInspectorControllerDelegate_('getDOMStorageEntries');
  this.installInspectorControllerDelegate_('getEventListenersForNode');
  this.installInspectorControllerDelegate_('highlightDOMNode');
  this.installInspectorControllerDelegate_('hideDOMNodeHighlight');
  this.installInspectorControllerDelegate_('releaseWrapperObjectGroup');
  this.installInspectorControllerDelegate_('removeAttribute');
  this.installInspectorControllerDelegate_('removeDOMStorageItem');
  this.installInspectorControllerDelegate_('removeNode');
  this.installInspectorControllerDelegate_('setAttribute');
  this.installInspectorControllerDelegate_('setDOMStorageItem');
  this.installInspectorControllerDelegate_('setSetting');
  this.installInspectorControllerDelegate_('setTextNodeValue');
  this.installInspectorControllerDelegate_('setting');
  this.installInspectorControllerDelegate_('startTimelineProfiler');
  this.installInspectorControllerDelegate_('stopTimelineProfiler');
  this.installInspectorControllerDelegate_('storeLastActivePanel');
};
goog.inherits(devtools.InspectorControllerImpl,
    WebInspector.InspectorControllerStub);


/**
 * {@inheritDoc}.
 */
devtools.InspectorControllerImpl.prototype.platform = function() {
  return DevToolsHost.getPlatform();
};


/**
 * {@inheritDoc}.
 */
devtools.InspectorControllerImpl.prototype.closeWindow = function() {
  DevToolsHost.closeWindow();
};


/**
 * {@inheritDoc}.
 */
devtools.InspectorControllerImpl.prototype.attach = function() {
  DevToolsHost.dockWindow();
};


/**
 * {@inheritDoc}.
 */
devtools.InspectorControllerImpl.prototype.detach = function() {
  DevToolsHost.undockWindow();
};


/**
 * {@inheritDoc}.
 */
devtools.InspectorControllerImpl.prototype.hiddenPanels = function() {
  return DevToolsHost.hiddenPanels();
};


/**
 * {@inheritDoc}.
 */
devtools.InspectorControllerImpl.prototype.search = function(sourceRow, query) {
  return DevToolsHost.search(sourceRow, query);
};


/**
 * {@inheritDoc}.
 */
devtools.InspectorControllerImpl.prototype.toggleNodeSearch = function() {
  WebInspector.InspectorControllerStub.prototype.toggleNodeSearch.call(this);
  this.callInspectorController_.call(this, 'toggleNodeSearch');
  if (!this.searchingForNode()) {
    // This is called from ElementsPanel treeOutline's focusNodeChanged().
    DevToolsHost.activateWindow();
  }
};


/**
 * {@inheritDoc}.
 */
devtools.InspectorControllerImpl.prototype.localizedStringsURL =
    function(opt_prefix) {
  // l10n is turned off in test mode because delayed loading of strings
  // causes test failures.
  if (false) {
    var locale = DevToolsHost.getApplicationLocale();
    locale = locale.replace('_', '-');
    return 'l10n/localizedStrings_' + locale + '.js';
  } else {
    return undefined;
  }
};


/**
 * {@inheritDoc}.
 */
devtools.InspectorControllerImpl.prototype.addSourceToFrame =
    function(mimeType, source, element) {
  return DevToolsHost.addSourceToFrame(mimeType, source, element);
};


/**
 * {@inheritDoc}.
 */
devtools.InspectorControllerImpl.prototype.addResourceSourceToFrame =
    function(identifier, element) {
  var resource = WebInspector.resources[identifier];
  if (!resource) {
    return;
  }

  // Temporary fix for http://crbug/23260.
  var mimeType = resource.mimeType;
  if (!mimeType && resource.url) {
    if (resource.url.search('\.js$') != -1) {
      mimeType = 'application/x-javascript';
    } else if (resource.url.search('\.html$') != -1) {
      mimeType = 'text/html';
    }
  }

  DevToolsHost.addResourceSourceToFrame(identifier, mimeType, element);
};


/**
 * {@inheritDoc}.
 */
devtools.InspectorControllerImpl.prototype.inspectedWindow = function() {
  return null;
};


/**
 * @override
 */
devtools.InspectorControllerImpl.prototype.debuggerEnabled = function() {
  return true;
};


/**
 * @override
 */
devtools.InspectorControllerImpl.prototype.profilerEnabled = function() {
  return true;
};


devtools.InspectorControllerImpl.prototype.addBreakpoint = function(
    sourceID, line, condition) {
  devtools.tools.getDebuggerAgent().addBreakpoint(sourceID, line, condition);
};


devtools.InspectorControllerImpl.prototype.removeBreakpoint = function(
    sourceID, line) {
  devtools.tools.getDebuggerAgent().removeBreakpoint(sourceID, line);
};

devtools.InspectorControllerImpl.prototype.updateBreakpoint = function(
    sourceID, line, condition) {
  devtools.tools.getDebuggerAgent().updateBreakpoint(
      sourceID, line, condition);
};

devtools.InspectorControllerImpl.prototype.pauseInDebugger = function() {
  devtools.tools.getDebuggerAgent().pauseExecution();
};


devtools.InspectorControllerImpl.prototype.resumeDebugger = function() {
  devtools.tools.getDebuggerAgent().resumeExecution();
};


devtools.InspectorControllerImpl.prototype.stepIntoStatementInDebugger =
    function() {
  devtools.tools.getDebuggerAgent().stepIntoStatement();
};


devtools.InspectorControllerImpl.prototype.stepOutOfFunctionInDebugger =
    function() {
  devtools.tools.getDebuggerAgent().stepOutOfFunction();
};


devtools.InspectorControllerImpl.prototype.stepOverStatementInDebugger =
    function() {
  devtools.tools.getDebuggerAgent().stepOverStatement();
};


/**
 * @override
 */
devtools.InspectorControllerImpl.prototype.pauseOnExceptions = function() {
  return devtools.tools.getDebuggerAgent().pauseOnExceptions();
};


/**
 * @override
 */
devtools.InspectorControllerImpl.prototype.setPauseOnExceptions = function(
    value) {
  return devtools.tools.getDebuggerAgent().setPauseOnExceptions(value);
};


/**
 * @override
 */
devtools.InspectorControllerImpl.prototype.startProfiling = function() {
  devtools.tools.getDebuggerAgent().startProfiling(
      devtools.DebuggerAgent.ProfilerModules.PROFILER_MODULE_CPU);
};


/**
 * @override
 */
devtools.InspectorControllerImpl.prototype.stopProfiling = function() {
  devtools.tools.getDebuggerAgent().stopProfiling(
      devtools.DebuggerAgent.ProfilerModules.PROFILER_MODULE_CPU);
};


/**
 * @override
 */
devtools.InspectorControllerImpl.prototype.getProfileHeaders = function(callId) {
  WebInspector.didGetProfileHeaders(callId, []);
};


/**
 * Emulate WebKit InspectorController behavior. It stores profiles on renderer side,
 * and is able to retrieve them by uid using 'getProfile'.
 */
devtools.InspectorControllerImpl.prototype.addFullProfile = function(profile) {
  WebInspector.__fullProfiles = WebInspector.__fullProfiles || {};
  WebInspector.__fullProfiles[profile.uid] = profile;
};


/**
 * @override
 */
devtools.InspectorControllerImpl.prototype.getProfile = function(callId, uid) {
  if (WebInspector.__fullProfiles && (uid in WebInspector.__fullProfiles)) {
    WebInspector.didGetProfile(callId, WebInspector.__fullProfiles[uid]);
  }
};


/**
 * @override
 */
devtools.InspectorControllerImpl.prototype.takeHeapSnapshot = function() {
  devtools.tools.getDebuggerAgent().startProfiling(
      devtools.DebuggerAgent.ProfilerModules.PROFILER_MODULE_HEAP_SNAPSHOT
      | devtools.DebuggerAgent.ProfilerModules.PROFILER_MODULE_HEAP_STATS
      | devtools.DebuggerAgent.ProfilerModules.PROFILER_MODULE_JS_CONSTRUCTORS);
};


/**
 * @override
 */
devtools.InspectorControllerImpl.prototype.dispatchOnInjectedScript = function(
    callId, methodName, argsString, async) {
  var callback = function(result, isException) {
    WebInspector.didDispatchOnInjectedScript(callId, result, isException);
  };
  RemoteToolsAgent.DispatchOnInjectedScript(
      WebInspector.Callback.wrap(callback),
      async ? methodName + "_async" : methodName,
      argsString);
};


/**
 * Installs delegating handler into the inspector controller.
 * @param {string} methodName Method to install delegating handler for.
 */
devtools.InspectorControllerImpl.prototype.installInspectorControllerDelegate_
    = function(methodName) {
  this[methodName] = goog.bind(this.callInspectorController_, this,
      methodName);
};


/**
 * Bound function with the installInjectedScriptDelegate_ actual
 * implementation.
 */
devtools.InspectorControllerImpl.prototype.callInspectorController_ =
    function(methodName, var_arg) {
  var args = Array.prototype.slice.call(arguments, 1);
  RemoteToolsAgent.DispatchOnInspectorController(
      WebInspector.Callback.wrap(function(){}),
      methodName,
      JSON.stringify(args));
};


InspectorController = new devtools.InspectorControllerImpl();

/* debugger_agent.js */

// Copyright (c) 2009 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/**
 * @fileoverview Provides communication interface to remote v8 debugger. See
 * protocol decription at http://code.google.com/p/v8/wiki/DebuggerProtocol
 */
goog.provide('devtools.DebuggerAgent');


/**
 * @constructor
 */
devtools.DebuggerAgent = function() {
  RemoteDebuggerAgent.DebuggerOutput =
      goog.bind(this.handleDebuggerOutput_, this);
  RemoteDebuggerAgent.SetContextId =
      goog.bind(this.setContextId_, this);
  RemoteDebuggerAgent.DidGetActiveProfilerModules =
      goog.bind(this.didGetActiveProfilerModules_, this);
  RemoteDebuggerAgent.DidGetNextLogLines =
      goog.bind(this.didGetNextLogLines_, this);

  /**
   * Id of the inspected page global context. It is used for filtering scripts.
   * @type {number}
   */
  this.contextId_ = null;

  /**
   * Mapping from script id to script info.
   * @type {Object}
   */
  this.parsedScripts_ = null;

  /**
   * Mapping from the request id to the devtools.BreakpointInfo for the
   * breakpoints whose v8 ids are not set yet. These breakpoints are waiting for
   * 'setbreakpoint' responses to learn their ids in the v8 debugger.
   * @see #handleSetBreakpointResponse_
   * @type {Object}
   */
  this.requestNumberToBreakpointInfo_ = null;

  /**
   * Information on current stack frames.
   * @type {Array.<devtools.CallFrame>}
   */
  this.callFrames_ = [];

  /**
   * Whether to stop in the debugger on the exceptions.
   * @type {boolean}
   */
  this.pauseOnExceptions_ = false;

  /**
   * Mapping: request sequence number->callback.
   * @type {Object}
   */
  this.requestSeqToCallback_ = null;

  /**
   * Whether the scripts panel has been shown and initialilzed.
   * @type {boolean}
   */
  this.scriptsPanelInitialized_ = false;

  /**
   * Whether the scripts list should be requested next time when context id is
   * set.
   * @type {boolean}
   */
  this.requestScriptsWhenContextIdSet_ = false;

  /**
   * Whether the agent is waiting for initial scripts response.
   * @type {boolean}
   */
  this.waitingForInitialScriptsResponse_ = false;

  /**
   * If backtrace response is received when initial scripts response
   * is not yet processed the backtrace handling will be postponed until
   * after the scripts response processing. The handler bound to its arguments
   * and this agent will be stored in this field then.
   * @type {?function()}
   */
  this.pendingBacktraceResponseHandler_ = null;

  /**
   * Active profiler modules flags.
   * @type {number}
   */
  this.activeProfilerModules_ =
      devtools.DebuggerAgent.ProfilerModules.PROFILER_MODULE_NONE;

  /**
   * Profiler processor instance.
   * @type {devtools.profiler.Processor}
   */
  this.profilerProcessor_ = new devtools.profiler.Processor();

  /**
   * Container of all breakpoints set using resource URL. These breakpoints
   * survive page reload. Breakpoints set by script id(for scripts that don't
   * have URLs) are stored in ScriptInfo objects.
   * @type {Object}
   */
  this.urlToBreakpoints_ = {};


  /**
   * Exception message that is shown to user while on exception break.
   * @type {WebInspector.ConsoleMessage}
   */
  this.currentExceptionMessage_ = null;
};


/**
 * A copy of the scope types from v8/src/mirror-delay.js
 * @enum {number}
 */
devtools.DebuggerAgent.ScopeType = {
  Global: 0,
  Local: 1,
  With: 2,
  Closure: 3,
  Catch: 4
};


/**
 * A copy of enum from include/v8.h
 * @enum {number}
 */
devtools.DebuggerAgent.ProfilerModules = {
  PROFILER_MODULE_NONE: 0,
  PROFILER_MODULE_CPU: 1,
  PROFILER_MODULE_HEAP_STATS: 1 << 1,
  PROFILER_MODULE_JS_CONSTRUCTORS: 1 << 2,
  PROFILER_MODULE_HEAP_SNAPSHOT: 1 << 16
};


/**
 * Resets debugger agent to its initial state.
 */
devtools.DebuggerAgent.prototype.reset = function() {
  this.contextId_ = null;
  // No need to request scripts since they all will be pushed in AfterCompile
  // events.
  this.requestScriptsWhenContextIdSet_ = false;
  this.waitingForInitialScriptsResponse_ = false;

  this.parsedScripts_ = {};
  this.requestNumberToBreakpointInfo_ = {};
  this.callFrames_ = [];
  this.requestSeqToCallback_ = {};

  // Profiler isn't reset because it contains no data that is
  // specific for a particular V8 instance. All such data is
  // managed by an agent on the Render's side.
};


/**
 * Initializes scripts UI. This method is called every time Scripts panel
 * is shown. It will send request for context id if it's not set yet.
 */
devtools.DebuggerAgent.prototype.initUI = function() {
  // Initialize scripts cache when Scripts panel is shown first time.
  if (this.scriptsPanelInitialized_) {
    return;
  }
  this.scriptsPanelInitialized_ = true;
  if (this.contextId_) {
    // We already have context id. This means that we are here from the
    // very beginning of the page load cycle and hence will get all scripts
    // via after-compile events. No need to request scripts for this session.
    //
    // There can be a number of scripts from after-compile events that are
    // pending addition into the UI.
    for (var scriptId in this.parsedScripts_) {
      var script = this.parsedScripts_[scriptId];
      WebInspector.parsedScriptSource(scriptId, script.getUrl(),
          undefined /* script source */, script.getLineOffset());
    }
    return;
  }
  this.waitingForInitialScriptsResponse_ = true;
  // Script list should be requested only when current context id is known.
  RemoteDebuggerAgent.GetContextId();
  this.requestScriptsWhenContextIdSet_ = true;
};


/**
 * Asynchronously requests the debugger for the script source.
 * @param {number} scriptId Id of the script whose source should be resolved.
 * @param {function(source:?string):void} callback Function that will be called
 *     when the source resolution is completed. 'source' parameter will be null
 *     if the resolution fails.
 */
devtools.DebuggerAgent.prototype.resolveScriptSource = function(
    scriptId, callback) {
  var script = this.parsedScripts_[scriptId];
  if (!script || script.isUnresolved()) {
    callback(null);
    return;
  }

  var cmd = new devtools.DebugCommand('scripts', {
    'ids': [scriptId],
    'includeSource': true
  });
  devtools.DebuggerAgent.sendCommand_(cmd);
  // Force v8 execution so that it gets to processing the requested command.
  RemoteToolsAgent.ExecuteVoidJavaScript();

  this.requestSeqToCallback_[cmd.getSequenceNumber()] = function(msg) {
    if (msg.isSuccess()) {
      var scriptJson = msg.getBody()[0];
      callback(scriptJson.source);
    } else {
      callback(null);
    }
  };
};


/**
 * Tells the v8 debugger to stop on as soon as possible.
 */
devtools.DebuggerAgent.prototype.pauseExecution = function() {
  RemoteDebuggerAgent.DebugBreak();
};


/**
 * @param {number} sourceId Id of the script fot the breakpoint.
 * @param {number} line Number of the line for the breakpoint.
 * @param {?string} condition The breakpoint condition.
 */
devtools.DebuggerAgent.prototype.addBreakpoint = function(
    sourceId, line, condition) {
  var script = this.parsedScripts_[sourceId];
  if (!script) {
    return;
  }

  line = devtools.DebuggerAgent.webkitToV8LineNumber_(line);

  var commandArguments;
  if (script.getUrl()) {
    var breakpoints = this.urlToBreakpoints_[script.getUrl()];
    if (breakpoints && breakpoints[line]) {
      return;
    }
    if (!breakpoints) {
      breakpoints = {};
      this.urlToBreakpoints_[script.getUrl()] = breakpoints;
    }

    var breakpointInfo = new devtools.BreakpointInfo(line);
    breakpoints[line] = breakpointInfo;

    commandArguments = {
      'groupId': this.contextId_,
      'type': 'script',
      'target': script.getUrl(),
      'line': line,
      'condition': condition
    };
  } else {
    var breakpointInfo = script.getBreakpointInfo(line);
    if (breakpointInfo) {
      return;
    }

    breakpointInfo = new devtools.BreakpointInfo(line);
    script.addBreakpointInfo(breakpointInfo);

    commandArguments = {
      'groupId': this.contextId_,
      'type': 'scriptId',
      'target': sourceId,
      'line': line,
      'condition': condition
    };
  }

  var cmd = new devtools.DebugCommand('setbreakpoint', commandArguments);

  this.requestNumberToBreakpointInfo_[cmd.getSequenceNumber()] = breakpointInfo;

  devtools.DebuggerAgent.sendCommand_(cmd);
  // Force v8 execution so that it gets to processing the requested command.
  // It is necessary for being able to change a breakpoint just after it
  // has been created (since we need an existing breakpoint id for that).
  RemoteToolsAgent.ExecuteVoidJavaScript();
};


/**
 * @param {number} sourceId Id of the script for the breakpoint.
 * @param {number} line Number of the line for the breakpoint.
 */
devtools.DebuggerAgent.prototype.removeBreakpoint = function(sourceId, line) {
  var script = this.parsedScripts_[sourceId];
  if (!script) {
    return;
  }

  line = devtools.DebuggerAgent.webkitToV8LineNumber_(line);

  var breakpointInfo;
  if (script.getUrl()) {
    var breakpoints = this.urlToBreakpoints_[script.getUrl()];
    breakpointInfo = breakpoints[line];
    delete breakpoints[line];
  } else {
    breakpointInfo = script.getBreakpointInfo(line);
    if (breakpointInfo) {
      script.removeBreakpointInfo(breakpointInfo);
    }
  }

  if (!breakpointInfo) {
    return;
  }

  breakpointInfo.markAsRemoved();

  var id = breakpointInfo.getV8Id();

  // If we don't know id of this breakpoint in the v8 debugger we cannot send
  // 'clearbreakpoint' request. In that case it will be removed in
  // 'setbreakpoint' response handler when we learn the id.
  if (id != -1) {
    this.requestClearBreakpoint_(id);
  }
};


/**
 * @param {number} sourceId Id of the script for the breakpoint.
 * @param {number} line Number of the line for the breakpoint.
 * @param {?string} condition New breakpoint condition.
 */
devtools.DebuggerAgent.prototype.updateBreakpoint = function(
    sourceId, line, condition) {
  var script = this.parsedScripts_[sourceId];
  if (!script) {
    return;
  }

  line = devtools.DebuggerAgent.webkitToV8LineNumber_(line);

  var breakpointInfo;
  if (script.getUrl()) {
    var breakpoints = this.urlToBreakpoints_[script.getUrl()];
    breakpointInfo = breakpoints[line];
  } else {
    breakpointInfo = script.getBreakpointInfo(line);
  }

  var id = breakpointInfo.getV8Id();

  // If we don't know id of this breakpoint in the v8 debugger we cannot send
  // the 'changebreakpoint' request.
  if (id != -1) {
    // TODO(apavlov): make use of the real values for 'enabled' and
    // 'ignoreCount' when appropriate.
    this.requestChangeBreakpoint_(id, true, condition, null);
  }
};


/**
 * Tells the v8 debugger to step into the next statement.
 */
devtools.DebuggerAgent.prototype.stepIntoStatement = function() {
  this.stepCommand_('in');
};


/**
 * Tells the v8 debugger to step out of current function.
 */
devtools.DebuggerAgent.prototype.stepOutOfFunction = function() {
  this.stepCommand_('out');
};


/**
 * Tells the v8 debugger to step over the next statement.
 */
devtools.DebuggerAgent.prototype.stepOverStatement = function() {
  this.stepCommand_('next');
};


/**
 * Tells the v8 debugger to continue execution after it has been stopped on a
 * breakpoint or an exception.
 */
devtools.DebuggerAgent.prototype.resumeExecution = function() {
  this.clearExceptionMessage_();
  var cmd = new devtools.DebugCommand('continue');
  devtools.DebuggerAgent.sendCommand_(cmd);
};


/**
 * Creates exception message and schedules it for addition to the resource upon
 * backtrace availability.
 * @param {string} url Resource url.
 * @param {number} line Resource line number.
 * @param {string} message Exception text.
 */
devtools.DebuggerAgent.prototype.createExceptionMessage_ = function(
    url, line, message) {
  this.currentExceptionMessage_ = new WebInspector.ConsoleMessage(
      WebInspector.ConsoleMessage.MessageSource.JS,
      WebInspector.ConsoleMessage.MessageType.Log,
      WebInspector.ConsoleMessage.MessageLevel.Error,
      line,
      url,
      0 /* group level */,
      1 /* repeat count */,
      '[Exception] ' + message);
};


/**
 * Shows pending exception message that is created with createExceptionMessage_
 * earlier.
 */
devtools.DebuggerAgent.prototype.showPendingExceptionMessage_ = function() {
  if (!this.currentExceptionMessage_) {
    return;
  }
  var msg = this.currentExceptionMessage_;
  var resource = WebInspector.resourceURLMap[msg.url];
  if (resource) {
    msg.resource = resource;
    WebInspector.panels.resources.addMessageToResource(resource, msg);
  } else {
    this.currentExceptionMessage_ = null;
  }
};


/**
 * Clears exception message from the resource.
 */
devtools.DebuggerAgent.prototype.clearExceptionMessage_ = function() {
  if (this.currentExceptionMessage_) {
    var messageElement =
        this.currentExceptionMessage_._resourceMessageLineElement;
    var bubble = messageElement.parentElement;
    bubble.removeChild(messageElement);
    if (!bubble.firstChild) {
      // Last message in bubble removed.
      bubble.parentElement.removeChild(bubble);
    }
    this.currentExceptionMessage_ = null;
  }
};


/**
 * @return {boolean} True iff the debugger will pause execution on the
 * exceptions.
 */
devtools.DebuggerAgent.prototype.pauseOnExceptions = function() {
  return this.pauseOnExceptions_;
};


/**
 * Tells whether to pause in the debugger on the exceptions or not.
 * @param {boolean} value True iff execution should be stopped in the debugger
 * on the exceptions.
 */
devtools.DebuggerAgent.prototype.setPauseOnExceptions = function(value) {
  this.pauseOnExceptions_ = value;
};


/**
 * Sends 'evaluate' request to the debugger.
 * @param {Object} arguments Request arguments map.
 * @param {function(devtools.DebuggerMessage)} callback Callback to be called
 *     when response is received.
 */
devtools.DebuggerAgent.prototype.requestEvaluate = function(
    arguments, callback) {
  var cmd = new devtools.DebugCommand('evaluate', arguments);
  devtools.DebuggerAgent.sendCommand_(cmd);
  this.requestSeqToCallback_[cmd.getSequenceNumber()] = callback;
};


/**
 * Sends 'lookup' request for each unresolved property of the object. When
 * response is received the properties will be changed with their resolved
 * values.
 * @param {Object} object Object whose properties should be resolved.
 * @param {function(devtools.DebuggerMessage)} Callback to be called when all
 *     children are resolved.
 * @param {boolean} noIntrinsic Whether intrinsic properties should be included.
 */
devtools.DebuggerAgent.prototype.resolveChildren = function(object, callback,
                                                            noIntrinsic) {
  if ('handle' in object) {
    var result = [];
    devtools.DebuggerAgent.formatObjectProperties_(object, result,
                                                   noIntrinsic);
    callback(result);
  } else {
    this.requestLookup_([object.ref], function(msg) {
      var result = [];
      if (msg.isSuccess()) {
        var handleToObject = msg.getBody();
        var resolved = handleToObject[object.ref];
        devtools.DebuggerAgent.formatObjectProperties_(resolved, result,
                                                       noIntrinsic);
        callback(result);
      } else {
        callback([]);
      }
    });
  }
};


/**
 * Sends 'scope' request for the scope object to resolve its variables.
 * @param {Object} scope Scope to be resolved.
 * @param {function(Array.<WebInspector.ObjectPropertyProxy>)} callback
 *     Callback to be called when all scope variables are resolved.
 */
devtools.DebuggerAgent.prototype.resolveScope = function(scope, callback) {
  var cmd = new devtools.DebugCommand('scope', {
    'frameNumber': scope.frameNumber,
    'number': scope.index,
    'compactFormat': true
  });
  devtools.DebuggerAgent.sendCommand_(cmd);
  this.requestSeqToCallback_[cmd.getSequenceNumber()] = function(msg) {
      var result = [];
    if (msg.isSuccess()) {
      var scopeObjectJson = msg.getBody().object;
      devtools.DebuggerAgent.formatObjectProperties_(scopeObjectJson, result,
                                                     true /* no intrinsic */);
    }
    callback(result);
  };
};


/**
 * Sends 'scopes' request for the frame object to resolve all variables
 * available in the frame.
 * @param {number} callFrameId Id of call frame whose variables need to
 *     be resolved.
 * @param {function(Object)} callback Callback to be called when all frame
 *     variables are resolved.
 */
devtools.DebuggerAgent.prototype.resolveFrameVariables_ = function(
    callFrameId, callback) {
  var result = {};

  var frame = this.callFrames_[callFrameId];
  if (!frame) {
    callback(result);
    return;
  }

  var waitingResponses = 0;
  function scopeResponseHandler(msg) {
    waitingResponses--;

    if (msg.isSuccess()) {
      var properties = msg.getBody().object.properties;
      for (var j = 0; j < properties.length; j++) {
        result[properties[j].name] = true;
      }
    }

    // When all scopes are resolved invoke the callback.
    if (waitingResponses == 0) {
      callback(result);
    }
  };

  for (var i = 0; i < frame.scopeChain.length; i++) {
    var scope = frame.scopeChain[i].objectId;
    if (scope.type == devtools.DebuggerAgent.ScopeType.Global) {
      // Do not resolve global scope since it takes for too long.
      // TODO(yurys): allow to send only property names in the response.
      continue;
    }
    var cmd = new devtools.DebugCommand('scope', {
      'frameNumber': scope.frameNumber,
      'number': scope.index,
      'compactFormat': true
    });
    devtools.DebuggerAgent.sendCommand_(cmd);
    this.requestSeqToCallback_[cmd.getSequenceNumber()] =
        scopeResponseHandler;
    waitingResponses++;
  }
};

/**
 * Evaluates the expressionString to an object in the call frame and reports
 * all its properties.
 * @param{string} expressionString Expression whose properties should be
 *     collected.
 * @param{number} callFrameId The frame id.
 * @param{function(Object result,bool isException)} reportCompletions Callback
 *     function.
 */
devtools.DebuggerAgent.prototype.resolveCompletionsOnFrame = function(
      expressionString, callFrameId, reportCompletions) {
    if (expressionString) {
      expressionString = 'var obj = ' + expressionString +
          '; var names = {}; for (var n in obj) { names[n] = true; };' +
          'names;';
      this.evaluateInCallFrame(
          callFrameId,
          expressionString,
          function(result) {
            var names = {};
            if (!result.isException) {
              var props = result.value.objectId.properties;
              // Put all object properties into the map.
              for (var i = 0; i < props.length; i++) {
                names[props[i].name] = true;
              }
            }
            reportCompletions(names, result.isException);
          });
    } else {
      this.resolveFrameVariables_(callFrameId,
          function(result) {
            reportCompletions(result, false /* isException */);
          });
    }
};


/**
 * Sets up callbacks that deal with profiles processing.
 */
devtools.DebuggerAgent.prototype.setupProfilerProcessorCallbacks = function() {
  // A temporary icon indicating that the profile is being processed.
  var processingIcon = new WebInspector.SidebarTreeElement(
      'profile-sidebar-tree-item',
      WebInspector.UIString('Processing...'),
      '', null, false);
  var profilesSidebar = WebInspector.panels.profiles.getProfileType(
      WebInspector.CPUProfileType.TypeId).treeElement;

  this.profilerProcessor_.setCallbacks(
      function onProfileProcessingStarted() {
        // Set visually empty string. Subtitle hiding is done via styles
        // manipulation which doesn't play well with dynamic append / removal.
        processingIcon.subtitle = ' ';
        profilesSidebar.appendChild(processingIcon);
      },
      function onProfileProcessingStatus(ticksCount) {
        processingIcon.subtitle =
            WebInspector.UIString('%d ticks processed', ticksCount);
      },
      function onProfileProcessingFinished(profile) {
        profilesSidebar.removeChild(processingIcon);
        profile.typeId = WebInspector.CPUProfileType.TypeId;
        InspectorController.addFullProfile(profile);
        WebInspector.addProfileHeader(profile);
        // If no profile is currently shown, show the new one.
        var profilesPanel = WebInspector.panels.profiles;
        if (!profilesPanel.visibleView) {
          profilesPanel.showProfile(profile);
        }
      }
  );
};


/**
 * Initializes profiling state.
 */
devtools.DebuggerAgent.prototype.initializeProfiling = function() {
  this.setupProfilerProcessorCallbacks();
  RemoteDebuggerAgent.GetActiveProfilerModules();
};


/**
 * Starts profiling.
 * @param {number} modules List of modules to enable.
 */
devtools.DebuggerAgent.prototype.startProfiling = function(modules) {
  RemoteDebuggerAgent.StartProfiling(modules);
  if (modules &
      devtools.DebuggerAgent.ProfilerModules.PROFILER_MODULE_HEAP_SNAPSHOT) {
    // Active modules will not change, instead, a snapshot will be logged.
    RemoteDebuggerAgent.GetNextLogLines();
  } else {
    RemoteDebuggerAgent.GetActiveProfilerModules();
  }
};


/**
 * Stops profiling.
 */
devtools.DebuggerAgent.prototype.stopProfiling = function(modules) {
  RemoteDebuggerAgent.StopProfiling(modules);
};


/**
 * @param{number} scriptId
 * @return {string} Type of the context of the script with specified id.
 */
devtools.DebuggerAgent.prototype.getScriptContextType = function(scriptId) {
  return this.parsedScripts_[scriptId].getContextType();
};


/**
 * Removes specified breakpoint from the v8 debugger.
 * @param {number} breakpointId Id of the breakpoint in the v8 debugger.
 */
devtools.DebuggerAgent.prototype.requestClearBreakpoint_ = function(
    breakpointId) {
  var cmd = new devtools.DebugCommand('clearbreakpoint', {
    'breakpoint': breakpointId
  });
  devtools.DebuggerAgent.sendCommand_(cmd);
};


/**
 * Changes breakpoint parameters in the v8 debugger.
 * @param {number} breakpointId Id of the breakpoint in the v8 debugger.
 * @param {boolean} enabled Whether to enable the breakpoint.
 * @param {?string} condition New breakpoint condition.
 * @param {number} ignoreCount New ignore count for the breakpoint.
 */
devtools.DebuggerAgent.prototype.requestChangeBreakpoint_ = function(
    breakpointId, enabled, condition, ignoreCount) {
  var cmd = new devtools.DebugCommand('changebreakpoint', {
    'breakpoint': breakpointId,
    'enabled': enabled,
    'condition': condition,
    'ignoreCount': ignoreCount
  });
  devtools.DebuggerAgent.sendCommand_(cmd);
};


/**
 * Sends 'backtrace' request to v8.
 */
devtools.DebuggerAgent.prototype.requestBacktrace_ = function() {
  var cmd = new devtools.DebugCommand('backtrace', {
    'compactFormat':true
  });
  devtools.DebuggerAgent.sendCommand_(cmd);
};


/**
 * Sends command to v8 debugger.
 * @param {devtools.DebugCommand} cmd Command to execute.
 */
devtools.DebuggerAgent.sendCommand_ = function(cmd) {
  RemoteDebuggerCommandExecutor.DebuggerCommand(cmd.toJSONProtocol());
};


/**
 * Tells the v8 debugger to make the next execution step.
 * @param {string} action 'in', 'out' or 'next' action.
 */
devtools.DebuggerAgent.prototype.stepCommand_ = function(action) {
  this.clearExceptionMessage_();
  var cmd = new devtools.DebugCommand('continue', {
    'stepaction': action,
    'stepcount': 1
  });
  devtools.DebuggerAgent.sendCommand_(cmd);
};


/**
 * Sends 'lookup' request to v8.
 * @param {number} handle Handle to the object to lookup.
 */
devtools.DebuggerAgent.prototype.requestLookup_ = function(handles, callback) {
  var cmd = new devtools.DebugCommand('lookup', {
    'compactFormat':true,
    'handles': handles
  });
  devtools.DebuggerAgent.sendCommand_(cmd);
  this.requestSeqToCallback_[cmd.getSequenceNumber()] = callback;
};


/**
 * Sets debugger context id for scripts filtering.
 * @param {number} contextId Id of the inspected page global context.
 */
devtools.DebuggerAgent.prototype.setContextId_ = function(contextId) {
  this.contextId_ = contextId;

  // If it's the first time context id is set request scripts list.
  if (this.requestScriptsWhenContextIdSet_) {
    this.requestScriptsWhenContextIdSet_ = false;
    var cmd = new devtools.DebugCommand('scripts', {
      'includeSource': false
    });
    devtools.DebuggerAgent.sendCommand_(cmd);
    // Force v8 execution so that it gets to processing the requested command.
    RemoteToolsAgent.ExecuteVoidJavaScript();

    var debuggerAgent = this;
    this.requestSeqToCallback_[cmd.getSequenceNumber()] = function(msg) {
      // Handle the response iff the context id hasn't changed since the request
      // was issued. Otherwise if the context id did change all up-to-date
      // scripts will be pushed in after compile events and there is no need to
      // handle the response.
      if (contextId == debuggerAgent.contextId_) {
        debuggerAgent.handleScriptsResponse_(msg);
      }

      // We received initial scripts response so flush the flag and
      // see if there is an unhandled backtrace response.
      debuggerAgent.waitingForInitialScriptsResponse_ = false;
      if (debuggerAgent.pendingBacktraceResponseHandler_) {
        debuggerAgent.pendingBacktraceResponseHandler_();
        debuggerAgent.pendingBacktraceResponseHandler_ = null;
      }
    };
  }
};


/**
 * Handles output sent by v8 debugger. The output is either asynchronous event
 * or response to a previously sent request.  See protocol definitioun for more
 * details on the output format.
 * @param {string} output
 */
devtools.DebuggerAgent.prototype.handleDebuggerOutput_ = function(output) {
  var msg;
  try {
    msg = new devtools.DebuggerMessage(output);
  } catch(e) {
    debugPrint('Failed to handle debugger reponse:\n' + e);
    throw e;
  }

  if (msg.getType() == 'event') {
    if (msg.getEvent() == 'break') {
      this.handleBreakEvent_(msg);
    } else if (msg.getEvent() == 'exception') {
      this.handleExceptionEvent_(msg);
    } else if (msg.getEvent() == 'afterCompile') {
      this.handleAfterCompileEvent_(msg);
    }
  } else if (msg.getType() == 'response') {
    if (msg.getCommand() == 'scripts') {
      this.invokeCallbackForResponse_(msg);
    } else if (msg.getCommand() == 'setbreakpoint') {
      this.handleSetBreakpointResponse_(msg);
    } else if (msg.getCommand() == 'clearbreakpoint') {
      this.handleClearBreakpointResponse_(msg);
    } else if (msg.getCommand() == 'backtrace') {
      this.handleBacktraceResponse_(msg);
    } else if (msg.getCommand() == 'lookup') {
      this.invokeCallbackForResponse_(msg);
    } else if (msg.getCommand() == 'evaluate') {
      this.invokeCallbackForResponse_(msg);
    } else if (msg.getCommand() == 'scope') {
      this.invokeCallbackForResponse_(msg);
    }
  }
};


/**
 * @param {devtools.DebuggerMessage} msg
 */
devtools.DebuggerAgent.prototype.handleBreakEvent_ = function(msg) {
  // Force scrips panel to be shown first.
  WebInspector.currentPanel = WebInspector.panels.scripts;

  var body = msg.getBody();

  var line = devtools.DebuggerAgent.v8ToWwebkitLineNumber_(body.sourceLine);
  this.requestBacktrace_();
};


/**
 * @param {devtools.DebuggerMessage} msg
 */
devtools.DebuggerAgent.prototype.handleExceptionEvent_ = function(msg) {
  // Force scrips panel to be shown first.
  WebInspector.currentPanel = WebInspector.panels.scripts;

  var body = msg.getBody();
  // No script field in the body means that v8 failed to parse the script. We
  // resume execution on parser errors automatically.
  if (this.pauseOnExceptions_ && body.script) {
    var line = devtools.DebuggerAgent.v8ToWwebkitLineNumber_(body.sourceLine);
    this.createExceptionMessage_(body.script.name, line, body.exception.text);
    this.requestBacktrace_();
  } else {
    this.resumeExecution();
  }
};


/**
 * @param {devtools.DebuggerMessage} msg
 */
devtools.DebuggerAgent.prototype.handleScriptsResponse_ = function(msg) {
  var scripts = msg.getBody();
  for (var i = 0; i < scripts.length; i++) {
    var script = scripts[i];

    // Skip scripts from other tabs.
    if (!this.isScriptFromInspectedContext_(script, msg)) {
      continue;
    }

    // We may already have received the info in an afterCompile event.
    if (script.id in this.parsedScripts_) {
      continue;
    }
    this.addScriptInfo_(script, msg);
  }
};


/**
 * @param {Object} script Json object representing script.
 * @param {devtools.DebuggerMessage} msg Debugger response.
 */
devtools.DebuggerAgent.prototype.isScriptFromInspectedContext_ = function(
    script, msg) {
  if (!script.context) {
    // Always ignore scripts from the utility context.
    return false;
  }
  var context = msg.lookup(script.context.ref);
  var scriptContextId = context.data;
  if (!goog.isDef(scriptContextId)) {
    return false; // Always ignore scripts from the utility context.
  }
  if (this.contextId_ === null) {
    return true;
  }
  if (goog.isString(context.data)) {
    // Find the id from context data. The context data has the format "type,id".
    var comma = context.data.indexOf(',');
    if (comma < 0) {
      return false;
    }
    return (parseInt(context.data.substring(comma + 1)) == this.contextId_);
  } else {
    // TODO(sgjesse) remove this when patch for
    // https://bugs.webkit.org/show_bug.cgi?id=31873 has landed in Chromium.
    return (scriptContextId.value == this.contextId_);
  }
};


/**
 * @param {devtools.DebuggerMessage} msg
 */
devtools.DebuggerAgent.prototype.handleSetBreakpointResponse_ = function(msg) {
  var requestSeq = msg.getRequestSeq();
  var breakpointInfo = this.requestNumberToBreakpointInfo_[requestSeq];
  if (!breakpointInfo) {
    // TODO(yurys): handle this case
    return;
  }
  delete this.requestNumberToBreakpointInfo_[requestSeq];
  if (!msg.isSuccess()) {
    // TODO(yurys): handle this case
    return;
  }
  var idInV8 = msg.getBody().breakpoint;
  breakpointInfo.setV8Id(idInV8);

  if (breakpointInfo.isRemoved()) {
    this.requestClearBreakpoint_(idInV8);
  }
};


/**
 * @param {devtools.DebuggerMessage} msg
 */
devtools.DebuggerAgent.prototype.handleAfterCompileEvent_ = function(msg) {
  if (!this.contextId_) {
    // Ignore scripts delta if main request has not been issued yet.
    return;
  }
  var script = msg.getBody().script;

  // Ignore scripts from other tabs.
  if (!this.isScriptFromInspectedContext_(script, msg)) {
    return;
  }
  this.addScriptInfo_(script, msg);
};


/**
 * Handles current profiler status.
 * @param {number} modules List of active (started) modules.
 */
devtools.DebuggerAgent.prototype.didGetActiveProfilerModules_ = function(
    modules) {
  var profModules = devtools.DebuggerAgent.ProfilerModules;
  var profModuleNone = profModules.PROFILER_MODULE_NONE;
  if (modules != profModuleNone &&
      this.activeProfilerModules_ == profModuleNone) {
    // Start to query log data.
    RemoteDebuggerAgent.GetNextLogLines();
  }
  this.activeProfilerModules_ = modules;
  // Update buttons.
  WebInspector.setRecordingProfile(modules & profModules.PROFILER_MODULE_CPU);
  if (modules != profModuleNone) {
    // Monitor profiler state. It can stop itself on buffer fill-up.
    setTimeout(
        function() { RemoteDebuggerAgent.GetActiveProfilerModules(); }, 1000);
  }
};


/**
 * Handles a portion of a profiler log retrieved by GetNextLogLines call.
 * @param {string} log A portion of profiler log.
 */
devtools.DebuggerAgent.prototype.didGetNextLogLines_ = function(log) {
  if (log.length > 0) {
    this.profilerProcessor_.processLogChunk(log);
  } else if (this.activeProfilerModules_ ==
      devtools.DebuggerAgent.ProfilerModules.PROFILER_MODULE_NONE) {
    // No new data and profiling is stopped---suspend log reading.
    return;
  }
  setTimeout(function() { RemoteDebuggerAgent.GetNextLogLines(); }, 500);
};


/**
 * Adds the script info to the local cache. This method assumes that the script
 * is not in the cache yet.
 * @param {Object} script Script json object from the debugger message.
 * @param {devtools.DebuggerMessage} msg Debugger message containing the script
 *     data.
 */
devtools.DebuggerAgent.prototype.addScriptInfo_ = function(script, msg) {
  var context = msg.lookup(script.context.ref);
  var contextType;
  if (goog.isString(context.data)) {
    // Find the type from context data. The context data has the format
    // "type,id".
    var comma = context.data.indexOf(',');
    if (comma < 0) {
      return
    }
    contextType = context.data.substring(0, comma);
  } else {
    // TODO(sgjesse) remove this when patch for
    // https://bugs.webkit.org/show_bug.cgi?id=31873 has landed in Chromium.
    contextType = context.data.type;
  }
  this.parsedScripts_[script.id] = new devtools.ScriptInfo(
      script.id, script.name, script.lineOffset, contextType);
  if (this.scriptsPanelInitialized_) {
    // Only report script as parsed after scripts panel has been shown.
    WebInspector.parsedScriptSource(
        script.id, script.name, script.source, script.lineOffset);
  }
};


/**
 * @param {devtools.DebuggerMessage} msg
 */
devtools.DebuggerAgent.prototype.handleClearBreakpointResponse_ = function(
    msg) {
  // Do nothing.
};


/**
 * Handles response to 'backtrace' command.
 * @param {devtools.DebuggerMessage} msg
 */
devtools.DebuggerAgent.prototype.handleBacktraceResponse_ = function(msg) {
  if (this.waitingForInitialScriptsResponse_) {
    this.pendingBacktraceResponseHandler_ =
        this.doHandleBacktraceResponse_.bind(this, msg);
  } else {
    this.doHandleBacktraceResponse_(msg);
  }
};


/**
 * @param {devtools.DebuggerMessage} msg
 */
devtools.DebuggerAgent.prototype.doHandleBacktraceResponse_ = function(msg) {
  var frames = msg.getBody().frames;
  this.callFrames_ = [];
  for (var i = 0; i <  frames.length; ++i) {
    this.callFrames_.push(this.formatCallFrame_(frames[i]));
  }
  WebInspector.pausedScript(this.callFrames_);
  this.showPendingExceptionMessage_();
  DevToolsHost.activateWindow();
};


/**
 * Evaluates code on given callframe.
 */
devtools.DebuggerAgent.prototype.evaluateInCallFrame = function(
    callFrameId, code, callback) {
  var callFrame = this.callFrames_[callFrameId];
  callFrame.evaluate_(code, callback);
};


/**
 * Handles response to a command by invoking its callback (if any).
 * @param {devtools.DebuggerMessage} msg
 * @return {boolean} Whether a callback for the given message was found and
 *     excuted.
 */
devtools.DebuggerAgent.prototype.invokeCallbackForResponse_ = function(msg) {
  var callback = this.requestSeqToCallback_[msg.getRequestSeq()];
  if (!callback) {
    // It may happend if reset was called.
    return false;
  }
  delete this.requestSeqToCallback_[msg.getRequestSeq()];
  callback(msg);
  return true;
};


/**
 * @param {Object} stackFrame Frame json object from 'backtrace' response.
 * @return {!devtools.CallFrame} Object containing information related to the
 *     call frame in the format expected by ScriptsPanel and its panes.
 */
devtools.DebuggerAgent.prototype.formatCallFrame_ = function(stackFrame) {
  var func = stackFrame.func;
  var sourceId = func.scriptId;

  // Add service script if it does not exist.
  var existingScript = this.parsedScripts_[sourceId];
  if (!existingScript) {
    this.parsedScripts_[sourceId] = new devtools.ScriptInfo(
        sourceId, null /* name */, 0 /* line */, 'unknown' /* type */,
        true /* unresolved */);
    WebInspector.parsedScriptSource(sourceId, null, null, 0);
  }

  var funcName = func.name || func.inferredName || '(anonymous function)';
  var line = devtools.DebuggerAgent.v8ToWwebkitLineNumber_(stackFrame.line);

  // Add basic scope chain info with scope variables.
  var scopeChain = [];
  var ScopeType = devtools.DebuggerAgent.ScopeType;
  for (var i = 0; i < stackFrame.scopes.length; i++) {
    var scope = stackFrame.scopes[i];
    scope.frameNumber = stackFrame.index;
    var scopeObjectProxy = new WebInspector.ObjectProxy(scope, [], 0, '', true);
    scopeObjectProxy.isScope = true;
    switch(scope.type) {
      case ScopeType.Global:
        scopeObjectProxy.isDocument = true;
        break;
      case ScopeType.Local:
        scopeObjectProxy.isLocal = true;
        scopeObjectProxy.thisObject =
            devtools.DebuggerAgent.formatObjectProxy_(stackFrame.receiver);
        break;
      case ScopeType.With:
      // Catch scope is treated as a regular with scope by WebKit so we
      // also treat it this way.
      case ScopeType.Catch:
        scopeObjectProxy.isWithBlock = true;
        break;
      case ScopeType.Closure:
        scopeObjectProxy.isClosure = true;
        break;
    }
    scopeChain.push(scopeObjectProxy);
  }
  return new devtools.CallFrame(stackFrame.index, 'function', funcName,
      sourceId, line, scopeChain);
};


/**
 * Collects properties for an object from the debugger response.
 * @param {Object} object An object from the debugger protocol response.
 * @param {Array.<WebInspector.ObjectPropertyProxy>} result An array to put the
 *     properties into.
 * @param {boolean} noIntrinsic Whether intrinsic properties should be
 *     included.
 */
devtools.DebuggerAgent.formatObjectProperties_ = function(object, result,
                                                          noIntrinsic) {
  devtools.DebuggerAgent.propertiesToProxies_(object.properties, result);
  if (noIntrinsic) {
    return;
  }

  result.push(new WebInspector.ObjectPropertyProxy('__proto__',
      devtools.DebuggerAgent.formatObjectProxy_(object.protoObject)));
  result.push(new WebInspector.ObjectPropertyProxy('constructor',
      devtools.DebuggerAgent.formatObjectProxy_(object.constructorFunction)));
  // Don't add 'prototype' property since it is one of the regualar properties.
};


/**
 * For each property in 'properties' creates its proxy representative.
 * @param {Array.<Object>} properties Receiver properties or locals array from
 *     'backtrace' response.
 * @param {Array.<WebInspector.ObjectPropertyProxy>} Results holder.
 */
devtools.DebuggerAgent.propertiesToProxies_ = function(properties, result) {
  var map = {};
  for (var i = 0; i < properties.length; ++i) {
    var property = properties[i];
    var name = String(property.name);
    if (name in map) {
      continue;
    }
    map[name] = true;
    var value = devtools.DebuggerAgent.formatObjectProxy_(property.value);
    var propertyProxy = new WebInspector.ObjectPropertyProxy(name, value);
    result.push(propertyProxy);
  }
};


/**
 * @param {Object} v An object reference from the debugger response.
 * @return {*} The value representation expected by ScriptsPanel.
 */
devtools.DebuggerAgent.formatObjectProxy_ = function(v) {
  var description;
  var hasChildren = false;
  if (v.type == 'object') {
    description = v.className;
    hasChildren = true;
  } else if (v.type == 'function') {
    if (v.source) {
      description = v.source;
    } else {
      description = 'function ' + v.name + '()';
    }
    hasChildren = true;
  } else if (v.type == 'undefined') {
    description = 'undefined';
  } else if (v.type == 'null') {
    description = 'null';
  } else if (goog.isDef(v.value)) {
    // Check for undefined and null types before checking the value, otherwise
    // null/undefined may have blank value.
    description = v.value;
  } else {
    description = '<unresolved ref: ' + v.ref + ', type: ' + v.type + '>';
  }
  var proxy = new WebInspector.ObjectProxy(v, [], 0, description, hasChildren);
  proxy.type = v.type;
  proxy.isV8Ref = true;
  return proxy;
};


/**
 * Converts line number from Web Inspector UI(1-based) to v8(0-based).
 * @param {number} line Resource line number in Web Inspector UI.
 * @return {number} The line number in v8.
 */
devtools.DebuggerAgent.webkitToV8LineNumber_ = function(line) {
  return line - 1;
};


/**
 * Converts line number from v8(0-based) to Web Inspector UI(1-based).
 * @param {number} line Resource line number in v8.
 * @return {number} The line number in Web Inspector.
 */
devtools.DebuggerAgent.v8ToWwebkitLineNumber_ = function(line) {
  return line + 1;
};


/**
 * @param {number} scriptId Id of the script.
 * @param {?string} url Script resource URL if any.
 * @param {number} lineOffset First line 0-based offset in the containing
 *     document.
 * @param {string} contextType Type of the script's context:
 *     "page" - regular script from html page
 *     "injected" - extension content script
 * @param {bool} opt_isUnresolved If true, script will not be resolved.
 * @constructor
 */
devtools.ScriptInfo = function(
    scriptId, url, lineOffset, contextType, opt_isUnresolved) {
  this.scriptId_ = scriptId;
  this.lineOffset_ = lineOffset;
  this.contextType_ = contextType;
  this.url_ = url;
  this.isUnresolved_ = opt_isUnresolved;

  this.lineToBreakpointInfo_ = {};
};


/**
 * @return {number}
 */
devtools.ScriptInfo.prototype.getLineOffset = function() {
  return this.lineOffset_;
};


/**
 * @return {string}
 */
devtools.ScriptInfo.prototype.getContextType = function() {
  return this.contextType_;
};


/**
 * @return {?string}
 */
devtools.ScriptInfo.prototype.getUrl = function() {
  return this.url_;
};


/**
 * @return {?bool}
 */
devtools.ScriptInfo.prototype.isUnresolved = function() {
  return this.isUnresolved_;
};


/**
 * @param {number} line 0-based line number in the script.
 * @return {?devtools.BreakpointInfo} Information on a breakpoint at the
 *     specified line in the script or undefined if there is no breakpoint at
 *     that line.
 */
devtools.ScriptInfo.prototype.getBreakpointInfo = function(line) {
  return this.lineToBreakpointInfo_[line];
};


/**
 * Adds breakpoint info to the script.
 * @param {devtools.BreakpointInfo} breakpoint
 */
devtools.ScriptInfo.prototype.addBreakpointInfo = function(breakpoint) {
  this.lineToBreakpointInfo_[breakpoint.getLine()] = breakpoint;
};


/**
 * @param {devtools.BreakpointInfo} breakpoint Breakpoint info to be removed.
 */
devtools.ScriptInfo.prototype.removeBreakpointInfo = function(breakpoint) {
  var line = breakpoint.getLine();
  delete this.lineToBreakpointInfo_[line];
};



/**
 * @param {number} line Breakpoint 0-based line number in the containing script.
 * @constructor
 */
devtools.BreakpointInfo = function(line) {
  this.line_ = line;
  this.v8id_ = -1;
  this.removed_ = false;
};


/**
 * @return {number}
 */
devtools.BreakpointInfo.prototype.getLine = function(n) {
  return this.line_;
};


/**
 * @return {number} Unique identifier of this breakpoint in the v8 debugger.
 */
devtools.BreakpointInfo.prototype.getV8Id = function(n) {
  return this.v8id_;
};


/**
 * Sets id of this breakpoint in the v8 debugger.
 * @param {number} id
 */
devtools.BreakpointInfo.prototype.setV8Id = function(id) {
  this.v8id_ = id;
};


/**
 * Marks this breakpoint as removed from the  front-end.
 */
devtools.BreakpointInfo.prototype.markAsRemoved = function() {
  this.removed_ = true;
};


/**
 * @return {boolean} Whether this breakpoint has been removed from the
 *     front-end.
 */
devtools.BreakpointInfo.prototype.isRemoved = function() {
  return this.removed_;
};


/**
 * Call stack frame data.
 * @param {string} id CallFrame id.
 * @param {string} type CallFrame type.
 * @param {string} functionName CallFrame type.
 * @param {string} sourceID Source id.
 * @param {number} line Source line.
 * @param {Array.<Object>} scopeChain Array of scoped objects.
 * @construnctor
 */
devtools.CallFrame = function(id, type, functionName, sourceID, line,
    scopeChain) {
  this.id = id;
  this.type = type;
  this.functionName = functionName;
  this.sourceID = sourceID;
  this.line = line;
  this.scopeChain = scopeChain;
};


/**
 * This method issues asynchronous evaluate request, reports result to the
 * callback.
 * @param {string} expression An expression to be evaluated in the context of
 *     this call frame.
 * @param {function(Object):undefined} callback Callback to report result to.
 */
devtools.CallFrame.prototype.evaluate_ = function(expression, callback) {
  devtools.tools.getDebuggerAgent().requestEvaluate({
      'expression': expression,
      'frame': this.id,
      'global': false,
      'disable_break': false,
      'compactFormat': true
    },
    function(response) {
      var result = {};
      if (response.isSuccess()) {
        result.value = devtools.DebuggerAgent.formatObjectProxy_(
            response.getBody());
      } else {
        result.value = response.getMessage();
        result.isException = true;
      }
      callback(result);
    });
};


/**
 * JSON based commands sent to v8 debugger.
 * @param {string} command Name of the command to execute.
 * @param {Object} opt_arguments Command-specific arguments map.
 * @constructor
 */
devtools.DebugCommand = function(command, opt_arguments) {
  this.command_ = command;
  this.type_ = 'request';
  this.seq_ = ++devtools.DebugCommand.nextSeq_;
  if (opt_arguments) {
    this.arguments_ = opt_arguments;
  }
};


/**
 * Next unique number to be used as debugger request sequence number.
 * @type {number}
 */
devtools.DebugCommand.nextSeq_ = 1;


/**
 * @return {number}
 */
devtools.DebugCommand.prototype.getSequenceNumber = function() {
  return this.seq_;
};


/**
 * @return {string}
 */
devtools.DebugCommand.prototype.toJSONProtocol = function() {
  var json = {
    'seq': this.seq_,
    'type': this.type_,
    'command': this.command_
  }
  if (this.arguments_) {
    json.arguments = this.arguments_;
  }
  return JSON.stringify(json);
};


/**
 * JSON messages sent from v8 debugger. See protocol definition for more
 * details: http://code.google.com/p/v8/wiki/DebuggerProtocol
 * @param {string} msg Raw protocol packet as JSON string.
 * @constructor
 */
devtools.DebuggerMessage = function(msg) {
  this.packet_ = JSON.parse(msg);
  this.refs_ = [];
  if (this.packet_.refs) {
    for (var i = 0; i < this.packet_.refs.length; i++) {
      this.refs_[this.packet_.refs[i].handle] = this.packet_.refs[i];
    }
  }
};


/**
 * @return {string} The packet type.
 */
devtools.DebuggerMessage.prototype.getType = function() {
  return this.packet_.type;
};


/**
 * @return {?string} The packet event if the message is an event.
 */
devtools.DebuggerMessage.prototype.getEvent = function() {
  return this.packet_.event;
};


/**
 * @return {?string} The packet command if the message is a response to a
 *     command.
 */
devtools.DebuggerMessage.prototype.getCommand = function() {
  return this.packet_.command;
};


/**
 * @return {number} The packet request sequence.
 */
devtools.DebuggerMessage.prototype.getRequestSeq = function() {
  return this.packet_.request_seq;
};


/**
 * @return {number} Whether the v8 is running after processing the request.
 */
devtools.DebuggerMessage.prototype.isRunning = function() {
  return this.packet_.running ? true : false;
};


/**
 * @return {boolean} Whether the request succeeded.
 */
devtools.DebuggerMessage.prototype.isSuccess = function() {
  return this.packet_.success ? true : false;
};


/**
 * @return {string}
 */
devtools.DebuggerMessage.prototype.getMessage = function() {
  return this.packet_.message;
};


/**
 * @return {Object} Parsed message body json.
 */
devtools.DebuggerMessage.prototype.getBody = function() {
  return this.packet_.body;
};


/**
 * @param {number} handle Object handle.
 * @return {?Object} Returns the object with the handle if it was sent in this
 *    message(some objects referenced by handles may be missing in the message).
 */
devtools.DebuggerMessage.prototype.lookup = function(handle) {
  return this.refs_[handle];
};

/* codemap.js */

// Copyright 2009 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
//       notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
//       copyright notice, this list of conditions and the following
//       disclaimer in the documentation and/or other materials provided
//       with the distribution.
//     * Neither the name of Google Inc. nor the names of its
//       contributors may be used to endorse or promote products derived
//       from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


// Initlialize namespaces
var devtools = devtools || {};
devtools.profiler = devtools.profiler || {};


/**
 * Constructs a mapper that maps addresses into code entries.
 *
 * @constructor
 */
devtools.profiler.CodeMap = function() {
  /**
   * Dynamic code entries. Used for JIT compiled code.
   */
  this.dynamics_ = new goog.structs.SplayTree();

  /**
   * Name generator for entries having duplicate names.
   */
  this.dynamicsNameGen_ = new devtools.profiler.CodeMap.NameGenerator();

  /**
   * Static code entries. Used for statically compiled code.
   */
  this.statics_ = new goog.structs.SplayTree();

  /**
   * Libraries entries. Used for the whole static code libraries.
   */
  this.libraries_ = new goog.structs.SplayTree();

  /**
   * Map of memory pages occupied with static code.
   */
  this.pages_ = [];
};


/**
 * The number of alignment bits in a page address.
 */
devtools.profiler.CodeMap.PAGE_ALIGNMENT = 12;


/**
 * Page size in bytes.
 */
devtools.profiler.CodeMap.PAGE_SIZE =
    1 << devtools.profiler.CodeMap.PAGE_ALIGNMENT;


/**
 * Adds a dynamic (i.e. moveable and discardable) code entry.
 *
 * @param {number} start The starting address.
 * @param {devtools.profiler.CodeMap.CodeEntry} codeEntry Code entry object.
 */
devtools.profiler.CodeMap.prototype.addCode = function(start, codeEntry) {
  this.dynamics_.insert(start, codeEntry);
};


/**
 * Moves a dynamic code entry. Throws an exception if there is no dynamic
 * code entry with the specified starting address.
 *
 * @param {number} from The starting address of the entry being moved.
 * @param {number} to The destination address.
 */
devtools.profiler.CodeMap.prototype.moveCode = function(from, to) {
  var removedNode = this.dynamics_.remove(from);
  this.dynamics_.insert(to, removedNode.value);
};


/**
 * Discards a dynamic code entry. Throws an exception if there is no dynamic
 * code entry with the specified starting address.
 *
 * @param {number} start The starting address of the entry being deleted.
 */
devtools.profiler.CodeMap.prototype.deleteCode = function(start) {
  var removedNode = this.dynamics_.remove(start);
};


/**
 * Adds a library entry.
 *
 * @param {number} start The starting address.
 * @param {devtools.profiler.CodeMap.CodeEntry} codeEntry Code entry object.
 */
devtools.profiler.CodeMap.prototype.addLibrary = function(
    start, codeEntry) {
  this.markPages_(start, start + codeEntry.size);
  this.libraries_.insert(start, codeEntry);
};


/**
 * Adds a static code entry.
 *
 * @param {number} start The starting address.
 * @param {devtools.profiler.CodeMap.CodeEntry} codeEntry Code entry object.
 */
devtools.profiler.CodeMap.prototype.addStaticCode = function(
    start, codeEntry) {
  this.statics_.insert(start, codeEntry);
};


/**
 * @private
 */
devtools.profiler.CodeMap.prototype.markPages_ = function(start, end) {
  for (var addr = start; addr <= end;
       addr += devtools.profiler.CodeMap.PAGE_SIZE) {
    this.pages_[addr >>> devtools.profiler.CodeMap.PAGE_ALIGNMENT] = 1;
  }
};


/**
 * @private
 */
devtools.profiler.CodeMap.prototype.isAddressBelongsTo_ = function(addr, node) {
  return addr >= node.key && addr < (node.key + node.value.size);
};


/**
 * @private
 */
devtools.profiler.CodeMap.prototype.findInTree_ = function(tree, addr) {
  var node = tree.findGreatestLessThan(addr);
  return node && this.isAddressBelongsTo_(addr, node) ? node.value : null;
};


/**
 * Finds a code entry that contains the specified address. Both static and
 * dynamic code entries are considered.
 *
 * @param {number} addr Address.
 */
devtools.profiler.CodeMap.prototype.findEntry = function(addr) {
  var pageAddr = addr >>> devtools.profiler.CodeMap.PAGE_ALIGNMENT;
  if (pageAddr in this.pages_) {
    // Static code entries can contain "holes" of unnamed code.
    // In this case, the whole library is assigned to this address.
    return this.findInTree_(this.statics_, addr) ||
        this.findInTree_(this.libraries_, addr);
  }
  var min = this.dynamics_.findMin();
  var max = this.dynamics_.findMax();
  if (max != null && addr < (max.key + max.value.size) && addr >= min.key) {
    var dynaEntry = this.findInTree_(this.dynamics_, addr);
    if (dynaEntry == null) return null;
    // Dedupe entry name.
    if (!dynaEntry.nameUpdated_) {
      dynaEntry.name = this.dynamicsNameGen_.getName(dynaEntry.name);
      dynaEntry.nameUpdated_ = true;
    }
    return dynaEntry;
  }
  return null;
};


/**
 * Returns an array of all dynamic code entries.
 */
devtools.profiler.CodeMap.prototype.getAllDynamicEntries = function() {
  return this.dynamics_.exportValues();
};


/**
 * Returns an array of all static code entries.
 */
devtools.profiler.CodeMap.prototype.getAllStaticEntries = function() {
  return this.statics_.exportValues();
};


/**
 * Returns an array of all libraries entries.
 */
devtools.profiler.CodeMap.prototype.getAllLibrariesEntries = function() {
  return this.libraries_.exportValues();
};


/**
 * Creates a code entry object.
 *
 * @param {number} size Code entry size in bytes.
 * @param {string} opt_name Code entry name.
 * @constructor
 */
devtools.profiler.CodeMap.CodeEntry = function(size, opt_name) {
  this.size = size;
  this.name = opt_name || '';
  this.nameUpdated_ = false;
};


devtools.profiler.CodeMap.CodeEntry.prototype.getName = function() {
  return this.name;
};


devtools.profiler.CodeMap.CodeEntry.prototype.toString = function() {
  return this.name + ': ' + this.size.toString(16);
};


devtools.profiler.CodeMap.NameGenerator = function() {
  this.knownNames_ = [];
};


devtools.profiler.CodeMap.NameGenerator.prototype.getName = function(name) {
  if (!(name in this.knownNames_)) {
    this.knownNames_[name] = 0;
    return name;
  }
  var count = ++this.knownNames_[name];
  return name + ' {' + count + '}';
};

/* consarray.js */

// Copyright 2009 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
//       notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
//       copyright notice, this list of conditions and the following
//       disclaimer in the documentation and/or other materials provided
//       with the distribution.
//     * Neither the name of Google Inc. nor the names of its
//       contributors may be used to endorse or promote products derived
//       from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


/**
 * Constructs a ConsArray object. It is used mainly for tree traversal.
 * In this use case we have lots of arrays that we need to iterate
 * sequentally. The internal Array implementation is horribly slow
 * when concatenating on large (10K items) arrays due to memory copying.
 * That's why we avoid copying memory and insead build a linked list
 * of arrays to iterate through.
 *
 * @constructor
 */
function ConsArray() {
  this.tail_ = new ConsArray.Cell(null, null);
  this.currCell_ = this.tail_;
  this.currCellPos_ = 0;
};


/**
 * Concatenates another array for iterating. Empty arrays are ignored.
 * This operation can be safely performed during ongoing ConsArray
 * iteration.
 *
 * @param {Array} arr Array to concatenate.
 */
ConsArray.prototype.concat = function(arr) {
  if (arr.length > 0) {
    this.tail_.data = arr;
    this.tail_ = this.tail_.next = new ConsArray.Cell(null, null);
  }
};


/**
 * Whether the end of iteration is reached.
 */
ConsArray.prototype.atEnd = function() {
  return this.currCell_ === null ||
      this.currCell_.data === null ||
      this.currCellPos_ >= this.currCell_.data.length;
};


/**
 * Returns the current item, moves to the next one.
 */
ConsArray.prototype.next = function() {
  var result = this.currCell_.data[this.currCellPos_++];
  if (this.currCellPos_ >= this.currCell_.data.length) {
    this.currCell_ = this.currCell_.next;
    this.currCellPos_ = 0;
  }
  return result;
};


/**
 * A cell object used for constructing a list in ConsArray.
 *
 * @constructor
 */
ConsArray.Cell = function(data, next) {
  this.data = data;
  this.next = next;
};


/* csvparser.js */

// Copyright 2009 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
//       notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
//       copyright notice, this list of conditions and the following
//       disclaimer in the documentation and/or other materials provided
//       with the distribution.
//     * Neither the name of Google Inc. nor the names of its
//       contributors may be used to endorse or promote products derived
//       from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


// Initlialize namespaces.
var devtools = devtools || {};
devtools.profiler = devtools.profiler || {};


/**
 * Creates a CSV lines parser.
 */
devtools.profiler.CsvParser = function() {
};


/**
 * A regex for matching a trailing quote.
 * @private
 */
devtools.profiler.CsvParser.TRAILING_QUOTE_RE_ = /\"$/;


/**
 * A regex for matching a double quote.
 * @private
 */
devtools.profiler.CsvParser.DOUBLE_QUOTE_RE_ = /\"\"/g;


/**
 * Parses a line of CSV-encoded values. Returns an array of fields.
 *
 * @param {string} line Input line.
 */
devtools.profiler.CsvParser.prototype.parseLine = function(line) {
  var insideQuotes = false;
  var fields = [];
  var prevPos = 0;
  for (var i = 0, n = line.length; i < n; ++i) {
    switch (line.charAt(i)) {
      case ',':
        if (!insideQuotes) {
          fields.push(line.substring(prevPos, i));
          prevPos = i + 1;
        }
        break;
      case '"':
        if (!insideQuotes) {
          insideQuotes = true;
          // Skip the leading quote.
          prevPos++;
        } else {
          if (i + 1 < n && line.charAt(i + 1) != '"') {
            insideQuotes = false;
          } else {
            i++;
          }
        }
        break;
    }
  }
  if (n > 0) {
    fields.push(line.substring(prevPos));
  }

  for (i = 0; i < fields.length; ++i) {
    // Eliminate trailing quotes.
    fields[i] = fields[i].replace(devtools.profiler.CsvParser.TRAILING_QUOTE_RE_, '');
    // Convert quoted quotes into single ones.
    fields[i] = fields[i].replace(devtools.profiler.CsvParser.DOUBLE_QUOTE_RE_, '"');
  }
  return fields;
};

/* logreader.js */

// Copyright 2009 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
//       notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
//       copyright notice, this list of conditions and the following
//       disclaimer in the documentation and/or other materials provided
//       with the distribution.
//     * Neither the name of Google Inc. nor the names of its
//       contributors may be used to endorse or promote products derived
//       from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

/**
 * @fileoverview Log Reader is used to process log file produced by V8.
 */

// Initlialize namespaces
var devtools = devtools || {};
devtools.profiler = devtools.profiler || {};


/**
 * Base class for processing log files.
 *
 * @param {Array.<Object>} dispatchTable A table used for parsing and processing
 *     log records.
 * @constructor
 */
devtools.profiler.LogReader = function(dispatchTable) {
  /**
   * @type {Array.<Object>}
   */
  this.dispatchTable_ = dispatchTable;
  this.dispatchTable_['alias'] =
      { parsers: [null, null], processor: this.processAlias_ };
  this.dispatchTable_['repeat'] =
      { parsers: [parseInt, 'var-args'], processor: this.processRepeat_,
        backrefs: true };

  /**
   * A key-value map for aliases. Translates short name -> full name.
   * @type {Object}
   */
  this.aliases_ = {};

  /**
   * A key-value map for previous address values.
   * @type {Object}
   */
  this.prevAddresses_ = {};

  /**
   * A key-value map for events than can be backreference-compressed.
   * @type {Object}
   */
  this.backRefsCommands_ = {};
  this.initBackRefsCommands_();

  /**
   * Back references for decompression.
   * @type {Array.<string>}
   */
  this.backRefs_ = [];
};


/**
 * Creates a parser for an address entry.
 *
 * @param {string} addressTag Address tag to perform offset decoding.
 * @return {function(string):number} Address parser.
 */
devtools.profiler.LogReader.prototype.createAddressParser = function(
    addressTag) {
  var self = this;
  return (function (str) {
    var value = parseInt(str, 16);
    var firstChar = str.charAt(0);
    if (firstChar == '+' || firstChar == '-') {
      var addr = self.prevAddresses_[addressTag];
      addr += value;
      self.prevAddresses_[addressTag] = addr;
      return addr;
    } else if (firstChar != '0' || str.charAt(1) != 'x') {
      self.prevAddresses_[addressTag] = value;
    }
    return value;
  });
};


/**
 * Expands an alias symbol, if applicable.
 *
 * @param {string} symbol Symbol to expand.
 * @return {string} Expanded symbol, or the input symbol itself.
 */
devtools.profiler.LogReader.prototype.expandAlias = function(symbol) {
  return symbol in this.aliases_ ? this.aliases_[symbol] : symbol;
};


/**
 * Used for printing error messages.
 *
 * @param {string} str Error message.
 */
devtools.profiler.LogReader.prototype.printError = function(str) {
  // Do nothing.
};


/**
 * Processes a portion of V8 profiler event log.
 *
 * @param {string} chunk A portion of log.
 */
devtools.profiler.LogReader.prototype.processLogChunk = function(chunk) {
  this.processLog_(chunk.split('\n'));
};


/**
 * Processes stack record.
 *
 * @param {number} pc Program counter.
 * @param {Array.<string>} stack String representation of a stack.
 * @return {Array.<number>} Processed stack.
 */
devtools.profiler.LogReader.prototype.processStack = function(pc, stack) {
  var fullStack = [pc];
  var prevFrame = pc;
  for (var i = 0, n = stack.length; i < n; ++i) {
    var frame = stack[i];
    var firstChar = frame.charAt(0);
    if (firstChar == '+' || firstChar == '-') {
      // An offset from the previous frame.
      prevFrame += parseInt(frame, 16);
      fullStack.push(prevFrame);
    // Filter out possible 'overflow' string.
    } else if (firstChar != 'o') {
      fullStack.push(parseInt(frame, 16));
    }
  }
  return fullStack;
};


/**
 * Returns whether a particular dispatch must be skipped.
 *
 * @param {!Object} dispatch Dispatch record.
 * @return {boolean} True if dispatch must be skipped.
 */
devtools.profiler.LogReader.prototype.skipDispatch = function(dispatch) {
  return false;
};


/**
 * Does a dispatch of a log record.
 *
 * @param {Array.<string>} fields Log record.
 * @private
 */
devtools.profiler.LogReader.prototype.dispatchLogRow_ = function(fields) {
  // Obtain the dispatch.
  var command = fields[0];
  if (!(command in this.dispatchTable_)) {
    throw new Error('unknown command: ' + command);
  }
  var dispatch = this.dispatchTable_[command];

  if (dispatch === null || this.skipDispatch(dispatch)) {
    return;
  }

  // Parse fields.
  var parsedFields = [];
  for (var i = 0; i < dispatch.parsers.length; ++i) {
    var parser = dispatch.parsers[i];
    if (parser === null) {
      parsedFields.push(fields[1 + i]);
    } else if (typeof parser == 'function') {
      parsedFields.push(parser(fields[1 + i]));
    } else {
      // var-args
      parsedFields.push(fields.slice(1 + i));
      break;
    }
  }

  // Run the processor.
  dispatch.processor.apply(this, parsedFields);
};


/**
 * Decompresses a line if it was backreference-compressed.
 *
 * @param {string} line Possibly compressed line.
 * @return {string} Decompressed line.
 * @private
 */
devtools.profiler.LogReader.prototype.expandBackRef_ = function(line) {
  var backRefPos;
  // Filter out case when a regexp is created containing '#'.
  if (line.charAt(line.length - 1) != '"'
      && (backRefPos = line.lastIndexOf('#')) != -1) {
    var backRef = line.substr(backRefPos + 1);
    var backRefIdx = parseInt(backRef, 10) - 1;
    var colonPos = backRef.indexOf(':');
    var backRefStart =
        colonPos != -1 ? parseInt(backRef.substr(colonPos + 1), 10) : 0;
    line = line.substr(0, backRefPos) +
        this.backRefs_[backRefIdx].substr(backRefStart);
  }
  this.backRefs_.unshift(line);
  if (this.backRefs_.length > 10) {
    this.backRefs_.length = 10;
  }
  return line;
};


/**
 * Initializes the map of backward reference compressible commands.
 * @private
 */
devtools.profiler.LogReader.prototype.initBackRefsCommands_ = function() {
  for (var event in this.dispatchTable_) {
    var dispatch = this.dispatchTable_[event];
    if (dispatch && dispatch.backrefs) {
      this.backRefsCommands_[event] = true;
    }
  }
};


/**
 * Processes alias log record. Adds an alias to a corresponding map.
 *
 * @param {string} symbol Short name.
 * @param {string} expansion Long name.
 * @private
 */
devtools.profiler.LogReader.prototype.processAlias_ = function(
    symbol, expansion) {
  if (expansion in this.dispatchTable_) {
    this.dispatchTable_[symbol] = this.dispatchTable_[expansion];
    if (expansion in this.backRefsCommands_) {
      this.backRefsCommands_[symbol] = true;
    }
  } else {
    this.aliases_[symbol] = expansion;
  }
};


/**
 * Processes log lines.
 *
 * @param {Array.<string>} lines Log lines.
 * @private
 */
devtools.profiler.LogReader.prototype.processLog_ = function(lines) {
  var csvParser = new devtools.profiler.CsvParser();
  try {
    for (var i = 0, n = lines.length; i < n; ++i) {
      var line = lines[i];
      if (!line) {
        continue;
      }
      if (line.charAt(0) == '#' ||
          line.substr(0, line.indexOf(',')) in this.backRefsCommands_) {
        line = this.expandBackRef_(line);
      }
      var fields = csvParser.parseLine(line);
      this.dispatchLogRow_(fields);
    }
  } catch (e) {
    // An error on the last line is acceptable since log file can be truncated.
    if (i < n - 1) {
      this.printError('line ' + (i + 1) + ': ' + (e.message || e));
      throw e;
    }
  }
};


/**
 * Processes repeat log record. Expands it according to calls count and
 * invokes processing.
 *
 * @param {number} count Count.
 * @param {Array.<string>} cmd Parsed command.
 * @private
 */
devtools.profiler.LogReader.prototype.processRepeat_ = function(count, cmd) {
  // Replace the repeat-prefixed command from backrefs list with a non-prefixed.
  this.backRefs_[0] = cmd.join(',');
  for (var i = 0; i < count; ++i) {
    this.dispatchLogRow_(cmd);
  }
};

/* profile.js */

// Copyright 2009 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
//       notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
//       copyright notice, this list of conditions and the following
//       disclaimer in the documentation and/or other materials provided
//       with the distribution.
//     * Neither the name of Google Inc. nor the names of its
//       contributors may be used to endorse or promote products derived
//       from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


// Initlialize namespaces
var devtools = devtools || {};
devtools.profiler = devtools.profiler || {};


/**
 * Creates a profile object for processing profiling-related events
 * and calculating function execution times.
 *
 * @constructor
 */
devtools.profiler.Profile = function() {
  this.codeMap_ = new devtools.profiler.CodeMap();
  this.topDownTree_ = new devtools.profiler.CallTree();
  this.bottomUpTree_ = new devtools.profiler.CallTree();
};


/**
 * Returns whether a function with the specified name must be skipped.
 * Should be overriden by subclasses.
 *
 * @param {string} name Function name.
 */
devtools.profiler.Profile.prototype.skipThisFunction = function(name) {
  return false;
};


/**
 * Enum for profiler operations that involve looking up existing
 * code entries.
 *
 * @enum {number}
 */
devtools.profiler.Profile.Operation = {
  MOVE: 0,
  DELETE: 1,
  TICK: 2
};


/**
 * Called whenever the specified operation has failed finding a function
 * containing the specified address. Should be overriden by subclasses.
 * See the devtools.profiler.Profile.Operation enum for the list of
 * possible operations.
 *
 * @param {number} operation Operation.
 * @param {number} addr Address of the unknown code.
 * @param {number} opt_stackPos If an unknown address is encountered
 *     during stack strace processing, specifies a position of the frame
 *     containing the address.
 */
devtools.profiler.Profile.prototype.handleUnknownCode = function(
    operation, addr, opt_stackPos) {
};


/**
 * Registers a library.
 *
 * @param {string} name Code entry name.
 * @param {number} startAddr Starting address.
 * @param {number} endAddr Ending address.
 */
devtools.profiler.Profile.prototype.addLibrary = function(
    name, startAddr, endAddr) {
  var entry = new devtools.profiler.CodeMap.CodeEntry(
      endAddr - startAddr, name);
  this.codeMap_.addLibrary(startAddr, entry);
  return entry;
};


/**
 * Registers statically compiled code entry.
 *
 * @param {string} name Code entry name.
 * @param {number} startAddr Starting address.
 * @param {number} endAddr Ending address.
 */
devtools.profiler.Profile.prototype.addStaticCode = function(
    name, startAddr, endAddr) {
  var entry = new devtools.profiler.CodeMap.CodeEntry(
      endAddr - startAddr, name);
  this.codeMap_.addStaticCode(startAddr, entry);
  return entry;
};


/**
 * Registers dynamic (JIT-compiled) code entry.
 *
 * @param {string} type Code entry type.
 * @param {string} name Code entry name.
 * @param {number} start Starting address.
 * @param {number} size Code entry size.
 */
devtools.profiler.Profile.prototype.addCode = function(
    type, name, start, size) {
  var entry = new devtools.profiler.Profile.DynamicCodeEntry(size, type, name);
  this.codeMap_.addCode(start, entry);
  return entry;
};


/**
 * Reports about moving of a dynamic code entry.
 *
 * @param {number} from Current code entry address.
 * @param {number} to New code entry address.
 */
devtools.profiler.Profile.prototype.moveCode = function(from, to) {
  try {
    this.codeMap_.moveCode(from, to);
  } catch (e) {
    this.handleUnknownCode(devtools.profiler.Profile.Operation.MOVE, from);
  }
};


/**
 * Reports about deletion of a dynamic code entry.
 *
 * @param {number} start Starting address.
 */
devtools.profiler.Profile.prototype.deleteCode = function(start) {
  try {
    this.codeMap_.deleteCode(start);
  } catch (e) {
    this.handleUnknownCode(devtools.profiler.Profile.Operation.DELETE, start);
  }
};


/**
 * Records a tick event. Stack must contain a sequence of
 * addresses starting with the program counter value.
 *
 * @param {Array<number>} stack Stack sample.
 */
devtools.profiler.Profile.prototype.recordTick = function(stack) {
  var processedStack = this.resolveAndFilterFuncs_(stack);
  this.bottomUpTree_.addPath(processedStack);
  processedStack.reverse();
  this.topDownTree_.addPath(processedStack);
};


/**
 * Translates addresses into function names and filters unneeded
 * functions.
 *
 * @param {Array<number>} stack Stack sample.
 */
devtools.profiler.Profile.prototype.resolveAndFilterFuncs_ = function(stack) {
  var result = [];
  for (var i = 0; i < stack.length; ++i) {
    var entry = this.codeMap_.findEntry(stack[i]);
    if (entry) {
      var name = entry.getName();
      if (!this.skipThisFunction(name)) {
        result.push(name);
      }
    } else {
      this.handleUnknownCode(
          devtools.profiler.Profile.Operation.TICK, stack[i], i);
    }
  }
  return result;
};


/**
 * Performs a BF traversal of the top down call graph.
 *
 * @param {function(devtools.profiler.CallTree.Node)} f Visitor function.
 */
devtools.profiler.Profile.prototype.traverseTopDownTree = function(f) {
  this.topDownTree_.traverse(f);
};


/**
 * Performs a BF traversal of the bottom up call graph.
 *
 * @param {function(devtools.profiler.CallTree.Node)} f Visitor function.
 */
devtools.profiler.Profile.prototype.traverseBottomUpTree = function(f) {
  this.bottomUpTree_.traverse(f);
};


/**
 * Calculates a top down profile for a node with the specified label.
 * If no name specified, returns the whole top down calls tree.
 *
 * @param {string} opt_label Node label.
 */
devtools.profiler.Profile.prototype.getTopDownProfile = function(opt_label) {
  return this.getTreeProfile_(this.topDownTree_, opt_label);
};


/**
 * Calculates a bottom up profile for a node with the specified label.
 * If no name specified, returns the whole bottom up calls tree.
 *
 * @param {string} opt_label Node label.
 */
devtools.profiler.Profile.prototype.getBottomUpProfile = function(opt_label) {
  return this.getTreeProfile_(this.bottomUpTree_, opt_label);
};


/**
 * Helper function for calculating a tree profile.
 *
 * @param {devtools.profiler.Profile.CallTree} tree Call tree.
 * @param {string} opt_label Node label.
 */
devtools.profiler.Profile.prototype.getTreeProfile_ = function(tree, opt_label) {
  if (!opt_label) {
    tree.computeTotalWeights();
    return tree;
  } else {
    var subTree = tree.cloneSubtree(opt_label);
    subTree.computeTotalWeights();
    return subTree;
  }
};


/**
 * Calculates a flat profile of callees starting from a node with
 * the specified label. If no name specified, starts from the root.
 *
 * @param {string} opt_label Starting node label.
 */
devtools.profiler.Profile.prototype.getFlatProfile = function(opt_label) {
  var counters = new devtools.profiler.CallTree();
  var rootLabel = opt_label || devtools.profiler.CallTree.ROOT_NODE_LABEL;
  var precs = {};
  precs[rootLabel] = 0;
  var root = counters.findOrAddChild(rootLabel);

  this.topDownTree_.computeTotalWeights();
  this.topDownTree_.traverseInDepth(
    function onEnter(node) {
      if (!(node.label in precs)) {
        precs[node.label] = 0;
      }
      var nodeLabelIsRootLabel = node.label == rootLabel;
      if (nodeLabelIsRootLabel || precs[rootLabel] > 0) {
        if (precs[rootLabel] == 0) {
          root.selfWeight += node.selfWeight;
          root.totalWeight += node.totalWeight;
        } else {
          var rec = root.findOrAddChild(node.label);
          rec.selfWeight += node.selfWeight;
          if (nodeLabelIsRootLabel || precs[node.label] == 0) {
            rec.totalWeight += node.totalWeight;
          }
        }
        precs[node.label]++;
      }
    },
    function onExit(node) {
      if (node.label == rootLabel || precs[rootLabel] > 0) {
        precs[node.label]--;
      }
    },
    null);

  if (!opt_label) {
    // If we have created a flat profile for the whole program, we don't
    // need an explicit root in it. Thus, replace the counters tree
    // root with the node corresponding to the whole program.
    counters.root_ = root;
  } else {
    // Propagate weights so percents can be calculated correctly.
    counters.getRoot().selfWeight = root.selfWeight;
    counters.getRoot().totalWeight = root.totalWeight;
  }
  return counters;
};


/**
 * Creates a dynamic code entry.
 *
 * @param {number} size Code size.
 * @param {string} type Code type.
 * @param {string} name Function name.
 * @constructor
 */
devtools.profiler.Profile.DynamicCodeEntry = function(size, type, name) {
  devtools.profiler.CodeMap.CodeEntry.call(this, size, name);
  this.type = type;
};


/**
 * Returns node name.
 */
devtools.profiler.Profile.DynamicCodeEntry.prototype.getName = function() {
  var name = this.name;
  if (name.length == 0) {
    name = '<anonymous>';
  } else if (name.charAt(0) == ' ') {
    // An anonymous function with location: " aaa.js:10".
    name = '<anonymous>' + name;
  }
  return this.type + ': ' + name;
};


/**
 * Constructs a call graph.
 *
 * @constructor
 */
devtools.profiler.CallTree = function() {
  this.root_ = new devtools.profiler.CallTree.Node(
      devtools.profiler.CallTree.ROOT_NODE_LABEL);
};


/**
 * The label of the root node.
 */
devtools.profiler.CallTree.ROOT_NODE_LABEL = '';


/**
 * @private
 */
devtools.profiler.CallTree.prototype.totalsComputed_ = false;


/**
 * Returns the tree root.
 */
devtools.profiler.CallTree.prototype.getRoot = function() {
  return this.root_;
};


/**
 * Adds the specified call path, constructing nodes as necessary.
 *
 * @param {Array<string>} path Call path.
 */
devtools.profiler.CallTree.prototype.addPath = function(path) {
  if (path.length == 0) {
    return;
  }
  var curr = this.root_;
  for (var i = 0; i < path.length; ++i) {
    curr = curr.findOrAddChild(path[i]);
  }
  curr.selfWeight++;
  this.totalsComputed_ = false;
};


/**
 * Finds an immediate child of the specified parent with the specified
 * label, creates a child node if necessary. If a parent node isn't
 * specified, uses tree root.
 *
 * @param {string} label Child node label.
 */
devtools.profiler.CallTree.prototype.findOrAddChild = function(label) {
  return this.root_.findOrAddChild(label);
};


/**
 * Creates a subtree by cloning and merging all subtrees rooted at nodes
 * with a given label. E.g. cloning the following call tree on label 'A'
 * will give the following result:
 *
 *           <A>--<B>                                     <B>
 *          /                                            /
 *     <root>             == clone on 'A' ==>  <root>--<A>
 *          \                                            \
 *           <C>--<A>--<D>                                <D>
 *
 * And <A>'s selfWeight will be the sum of selfWeights of <A>'s from the
 * source call tree.
 *
 * @param {string} label The label of the new root node.
 */
devtools.profiler.CallTree.prototype.cloneSubtree = function(label) {
  var subTree = new devtools.profiler.CallTree();
  this.traverse(function(node, parent) {
    if (!parent && node.label != label) {
      return null;
    }
    var child = (parent ? parent : subTree).findOrAddChild(node.label);
    child.selfWeight += node.selfWeight;
    return child;
  });
  return subTree;
};


/**
 * Computes total weights in the call graph.
 */
devtools.profiler.CallTree.prototype.computeTotalWeights = function() {
  if (this.totalsComputed_) {
    return;
  }
  this.root_.computeTotalWeight();
  this.totalsComputed_ = true;
};


/**
 * Traverses the call graph in preorder. This function can be used for
 * building optionally modified tree clones. This is the boilerplate code
 * for this scenario:
 *
 * callTree.traverse(function(node, parentClone) {
 *   var nodeClone = cloneNode(node);
 *   if (parentClone)
 *     parentClone.addChild(nodeClone);
 *   return nodeClone;
 * });
 *
 * @param {function(devtools.profiler.CallTree.Node, *)} f Visitor function.
 *    The second parameter is the result of calling 'f' on the parent node.
 */
devtools.profiler.CallTree.prototype.traverse = function(f) {
  var pairsToProcess = new ConsArray();
  pairsToProcess.concat([{node: this.root_, param: null}]);
  while (!pairsToProcess.atEnd()) {
    var pair = pairsToProcess.next();
    var node = pair.node;
    var newParam = f(node, pair.param);
    var morePairsToProcess = [];
    node.forEachChild(function (child) {
        morePairsToProcess.push({node: child, param: newParam}); });
    pairsToProcess.concat(morePairsToProcess);
  }
};


/**
 * Performs an indepth call graph traversal.
 *
 * @param {function(devtools.profiler.CallTree.Node)} enter A function called
 *     prior to visiting node's children.
 * @param {function(devtools.profiler.CallTree.Node)} exit A function called
 *     after visiting node's children.
 */
devtools.profiler.CallTree.prototype.traverseInDepth = function(enter, exit) {
  function traverse(node) {
    enter(node);
    node.forEachChild(traverse);
    exit(node);
  }
  traverse(this.root_);
};


/**
 * Constructs a call graph node.
 *
 * @param {string} label Node label.
 * @param {devtools.profiler.CallTree.Node} opt_parent Node parent.
 */
devtools.profiler.CallTree.Node = function(label, opt_parent) {
  this.label = label;
  this.parent = opt_parent;
  this.children = {};
};


/**
 * Node self weight (how many times this node was the last node in
 * a call path).
 * @type {number}
 */
devtools.profiler.CallTree.Node.prototype.selfWeight = 0;


/**
 * Node total weight (includes weights of all children).
 * @type {number}
 */
devtools.profiler.CallTree.Node.prototype.totalWeight = 0;


/**
 * Adds a child node.
 *
 * @param {string} label Child node label.
 */
devtools.profiler.CallTree.Node.prototype.addChild = function(label) {
  var child = new devtools.profiler.CallTree.Node(label, this);
  this.children[label] = child;
  return child;
};


/**
 * Computes node's total weight.
 */
devtools.profiler.CallTree.Node.prototype.computeTotalWeight =
    function() {
  var totalWeight = this.selfWeight;
  this.forEachChild(function(child) {
      totalWeight += child.computeTotalWeight(); });
  return this.totalWeight = totalWeight;
};


/**
 * Returns all node's children as an array.
 */
devtools.profiler.CallTree.Node.prototype.exportChildren = function() {
  var result = [];
  this.forEachChild(function (node) { result.push(node); });
  return result;
};


/**
 * Finds an immediate child with the specified label.
 *
 * @param {string} label Child node label.
 */
devtools.profiler.CallTree.Node.prototype.findChild = function(label) {
  return this.children[label] || null;
};


/**
 * Finds an immediate child with the specified label, creates a child
 * node if necessary.
 *
 * @param {string} label Child node label.
 */
devtools.profiler.CallTree.Node.prototype.findOrAddChild = function(label) {
  return this.findChild(label) || this.addChild(label);
};


/**
 * Calls the specified function for every child.
 *
 * @param {function(devtools.profiler.CallTree.Node)} f Visitor function.
 */
devtools.profiler.CallTree.Node.prototype.forEachChild = function(f) {
  for (var c in this.children) {
    f(this.children[c]);
  }
};


/**
 * Walks up from the current node up to the call tree root.
 *
 * @param {function(devtools.profiler.CallTree.Node)} f Visitor function.
 */
devtools.profiler.CallTree.Node.prototype.walkUpToRoot = function(f) {
  for (var curr = this; curr != null; curr = curr.parent) {
    f(curr);
  }
};


/**
 * Tries to find a node with the specified path.
 *
 * @param {Array<string>} labels The path.
 * @param {function(devtools.profiler.CallTree.Node)} opt_f Visitor function.
 */
devtools.profiler.CallTree.Node.prototype.descendToChild = function(
    labels, opt_f) {
  for (var pos = 0, curr = this; pos < labels.length && curr != null; pos++) {
    var child = curr.findChild(labels[pos]);
    if (opt_f) {
      opt_f(child, pos);
    }
    curr = child;
  }
  return curr;
};

/* profile_view.js */

// Copyright 2009 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
//       notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
//       copyright notice, this list of conditions and the following
//       disclaimer in the documentation and/or other materials provided
//       with the distribution.
//     * Neither the name of Google Inc. nor the names of its
//       contributors may be used to endorse or promote products derived
//       from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


// Initlialize namespaces
var devtools = devtools || {};
devtools.profiler = devtools.profiler || {};


/**
 * Creates a Profile View builder object.
 *
 * @param {number} samplingRate Number of ms between profiler ticks.
 * @constructor
 */
devtools.profiler.ViewBuilder = function(samplingRate) {
  this.samplingRate = samplingRate;
};


/**
 * Builds a profile view for the specified call tree.
 *
 * @param {devtools.profiler.CallTree} callTree A call tree.
 * @param {boolean} opt_bottomUpViewWeights Whether remapping
 *     of self weights for a bottom up view is needed.
 */
devtools.profiler.ViewBuilder.prototype.buildView = function(
    callTree, opt_bottomUpViewWeights) {
  var head;
  var samplingRate = this.samplingRate;
  var createViewNode = this.createViewNode;
  callTree.traverse(function(node, viewParent) {
    var totalWeight = node.totalWeight * samplingRate;
    var selfWeight = node.selfWeight * samplingRate;
    if (opt_bottomUpViewWeights === true) {
      if (viewParent === head) {
        selfWeight = totalWeight;
      } else {
        selfWeight = 0;
      }
    }
    var viewNode = createViewNode(node.label, totalWeight, selfWeight, head);
    if (viewParent) {
      viewParent.addChild(viewNode);
    } else {
      head = viewNode;
    }
    return viewNode;
  });
  var view = this.createView(head);
  return view;
};


/**
 * Factory method for a profile view.
 *
 * @param {devtools.profiler.ProfileView.Node} head View head node.
 * @return {devtools.profiler.ProfileView} Profile view.
 */
devtools.profiler.ViewBuilder.prototype.createView = function(head) {
  return new devtools.profiler.ProfileView(head);
};


/**
 * Factory method for a profile view node.
 *
 * @param {string} internalFuncName A fully qualified function name.
 * @param {number} totalTime Amount of time that application spent in the
 *     corresponding function and its descendants (not that depending on
 *     profile they can be either callees or callers.)
 * @param {number} selfTime Amount of time that application spent in the
 *     corresponding function only.
 * @param {devtools.profiler.ProfileView.Node} head Profile view head.
 * @return {devtools.profiler.ProfileView.Node} Profile view node.
 */
devtools.profiler.ViewBuilder.prototype.createViewNode = function(
    funcName, totalTime, selfTime, head) {
  return new devtools.profiler.ProfileView.Node(
      funcName, totalTime, selfTime, head);
};


/**
 * Creates a Profile View object. It allows to perform sorting
 * and filtering actions on the profile.
 *
 * @param {devtools.profiler.ProfileView.Node} head Head (root) node.
 * @constructor
 */
devtools.profiler.ProfileView = function(head) {
  this.head = head;
};


/**
 * Sorts the profile view using the specified sort function.
 *
 * @param {function(devtools.profiler.ProfileView.Node,
 *     devtools.profiler.ProfileView.Node):number} sortFunc A sorting
 *     functions. Must comply with Array.sort sorting function requirements.
 */
devtools.profiler.ProfileView.prototype.sort = function(sortFunc) {
  this.traverse(function (node) {
    node.sortChildren(sortFunc);
  });
};


/**
 * Traverses profile view nodes in preorder.
 *
 * @param {function(devtools.profiler.ProfileView.Node)} f Visitor function.
 */
devtools.profiler.ProfileView.prototype.traverse = function(f) {
  var nodesToTraverse = new ConsArray();
  nodesToTraverse.concat([this.head]);
  while (!nodesToTraverse.atEnd()) {
    var node = nodesToTraverse.next();
    f(node);
    nodesToTraverse.concat(node.children);
  }
};


/**
 * Constructs a Profile View node object. Each node object corresponds to
 * a function call.
 *
 * @param {string} internalFuncName A fully qualified function name.
 * @param {number} totalTime Amount of time that application spent in the
 *     corresponding function and its descendants (not that depending on
 *     profile they can be either callees or callers.)
 * @param {number} selfTime Amount of time that application spent in the
 *     corresponding function only.
 * @param {devtools.profiler.ProfileView.Node} head Profile view head.
 * @constructor
 */
devtools.profiler.ProfileView.Node = function(
    internalFuncName, totalTime, selfTime, head) {
  this.internalFuncName = internalFuncName;
  this.totalTime = totalTime;
  this.selfTime = selfTime;
  this.head = head;
  this.parent = null;
  this.children = [];
};


/**
 * Returns a share of the function's total time in application's total time.
 */
devtools.profiler.ProfileView.Node.prototype.__defineGetter__(
    'totalPercent',
    function() { return this.totalTime /
      (this.head ? this.head.totalTime : this.totalTime) * 100.0; });


/**
 * Returns a share of the function's self time in application's total time.
 */
devtools.profiler.ProfileView.Node.prototype.__defineGetter__(
    'selfPercent',
    function() { return this.selfTime /
      (this.head ? this.head.totalTime : this.totalTime) * 100.0; });


/**
 * Returns a share of the function's total time in its parent's total time.
 */
devtools.profiler.ProfileView.Node.prototype.__defineGetter__(
    'parentTotalPercent',
    function() { return this.totalTime /
      (this.parent ? this.parent.totalTime : this.totalTime) * 100.0; });


/**
 * Adds a child to the node.
 *
 * @param {devtools.profiler.ProfileView.Node} node Child node.
 */
devtools.profiler.ProfileView.Node.prototype.addChild = function(node) {
  node.parent = this;
  this.children.push(node);
};


/**
 * Sorts all the node's children recursively.
 *
 * @param {function(devtools.profiler.ProfileView.Node,
 *     devtools.profiler.ProfileView.Node):number} sortFunc A sorting
 *     functions. Must comply with Array.sort sorting function requirements.
 */
devtools.profiler.ProfileView.Node.prototype.sortChildren = function(
    sortFunc) {
  this.children.sort(sortFunc);
};

/* splaytree.js */

// Copyright 2009 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
//       notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
//       copyright notice, this list of conditions and the following
//       disclaimer in the documentation and/or other materials provided
//       with the distribution.
//     * Neither the name of Google Inc. nor the names of its
//       contributors may be used to endorse or promote products derived
//       from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


// A namespace stub. It will become more clear how to declare it properly
// during integration of this script into Dev Tools.
var goog = goog || {};
goog.structs = goog.structs || {};


/**
 * Constructs a Splay tree.  A splay tree is a self-balancing binary
 * search tree with the additional property that recently accessed
 * elements are quick to access again. It performs basic operations
 * such as insertion, look-up and removal in O(log(n)) amortized time.
 *
 * @constructor
 */
goog.structs.SplayTree = function() {
};


/**
 * Pointer to the root node of the tree.
 *
 * @type {goog.structs.SplayTree.Node}
 * @private
 */
goog.structs.SplayTree.prototype.root_ = null;


/**
 * @return {boolean} Whether the tree is empty.
 */
goog.structs.SplayTree.prototype.isEmpty = function() {
  return !this.root_;
};



/**
 * Inserts a node into the tree with the specified key and value if
 * the tree does not already contain a node with the specified key. If
 * the value is inserted, it becomes the root of the tree.
 *
 * @param {number} key Key to insert into the tree.
 * @param {*} value Value to insert into the tree.
 */
goog.structs.SplayTree.prototype.insert = function(key, value) {
  if (this.isEmpty()) {
    this.root_ = new goog.structs.SplayTree.Node(key, value);
    return;
  }
  // Splay on the key to move the last node on the search path for
  // the key to the root of the tree.
  this.splay_(key);
  if (this.root_.key == key) {
    return;
  }
  var node = new goog.structs.SplayTree.Node(key, value);
  if (key > this.root_.key) {
    node.left = this.root_;
    node.right = this.root_.right;
    this.root_.right = null;
  } else {
    node.right = this.root_;
    node.left = this.root_.left;
    this.root_.left = null;
  }
  this.root_ = node;
};


/**
 * Removes a node with the specified key from the tree if the tree
 * contains a node with this key. The removed node is returned. If the
 * key is not found, an exception is thrown.
 *
 * @param {number} key Key to find and remove from the tree.
 * @return {goog.structs.SplayTree.Node} The removed node.
 */
goog.structs.SplayTree.prototype.remove = function(key) {
  if (this.isEmpty()) {
    throw Error('Key not found: ' + key);
  }
  this.splay_(key);
  if (this.root_.key != key) {
    throw Error('Key not found: ' + key);
  }
  var removed = this.root_;
  if (!this.root_.left) {
    this.root_ = this.root_.right;
  } else {
    var right = this.root_.right;
    this.root_ = this.root_.left;
    // Splay to make sure that the new root has an empty right child.
    this.splay_(key);
    // Insert the original right child as the right child of the new
    // root.
    this.root_.right = right;
  }
  return removed;
};


/**
 * Returns the node having the specified key or null if the tree doesn't contain
 * a node with the specified key.
 *
 * @param {number} key Key to find in the tree.
 * @return {goog.structs.SplayTree.Node} Node having the specified key.
 */
goog.structs.SplayTree.prototype.find = function(key) {
  if (this.isEmpty()) {
    return null;
  }
  this.splay_(key);
  return this.root_.key == key ? this.root_ : null;
};


/**
 * @return {goog.structs.SplayTree.Node} Node having the minimum key value.
 */
goog.structs.SplayTree.prototype.findMin = function() {
  if (this.isEmpty()) {
    return null;
  }
  var current = this.root_;
  while (current.left) {
    current = current.left;
  }
  return current;
};


/**
 * @return {goog.structs.SplayTree.Node} Node having the maximum key value.
 */
goog.structs.SplayTree.prototype.findMax = function(opt_startNode) {
  if (this.isEmpty()) {
    return null;
  }
  var current = opt_startNode || this.root_;
  while (current.right) {
    current = current.right;
  }
  return current;
};


/**
 * @return {goog.structs.SplayTree.Node} Node having the maximum key value that
 *     is less or equal to the specified key value.
 */
goog.structs.SplayTree.prototype.findGreatestLessThan = function(key) {
  if (this.isEmpty()) {
    return null;
  }
  // Splay on the key to move the node with the given key or the last
  // node on the search path to the top of the tree.
  this.splay_(key);
  // Now the result is either the root node or the greatest node in
  // the left subtree.
  if (this.root_.key <= key) {
    return this.root_;
  } else if (this.root_.left) {
    return this.findMax(this.root_.left);
  } else {
    return null;
  }
};


/**
 * @return {Array<*>} An array containing all the values of tree's nodes.
 */
goog.structs.SplayTree.prototype.exportValues = function() {
  var result = [];
  this.traverse_(function(node) { result.push(node.value); });
  return result;
};


/**
 * Perform the splay operation for the given key. Moves the node with
 * the given key to the top of the tree.  If no node has the given
 * key, the last node on the search path is moved to the top of the
 * tree. This is the simplified top-down splaying algorithm from:
 * "Self-adjusting Binary Search Trees" by Sleator and Tarjan
 *
 * @param {number} key Key to splay the tree on.
 * @private
 */
goog.structs.SplayTree.prototype.splay_ = function(key) {
  if (this.isEmpty()) {
    return;
  }
  // Create a dummy node.  The use of the dummy node is a bit
  // counter-intuitive: The right child of the dummy node will hold
  // the L tree of the algorithm.  The left child of the dummy node
  // will hold the R tree of the algorithm.  Using a dummy node, left
  // and right will always be nodes and we avoid special cases.
  var dummy, left, right;
  dummy = left = right = new goog.structs.SplayTree.Node(null, null);
  var current = this.root_;
  while (true) {
    if (key < current.key) {
      if (!current.left) {
        break;
      }
      if (key < current.left.key) {
        // Rotate right.
        var tmp = current.left;
        current.left = tmp.right;
        tmp.right = current;
        current = tmp;
        if (!current.left) {
          break;
        }
      }
      // Link right.
      right.left = current;
      right = current;
      current = current.left;
    } else if (key > current.key) {
      if (!current.right) {
        break;
      }
      if (key > current.right.key) {
        // Rotate left.
        var tmp = current.right;
        current.right = tmp.left;
        tmp.left = current;
        current = tmp;
        if (!current.right) {
          break;
        }
      }
      // Link left.
      left.right = current;
      left = current;
      current = current.right;
    } else {
      break;
    }
  }
  // Assemble.
  left.right = current.left;
  right.left = current.right;
  current.left = dummy.right;
  current.right = dummy.left;
  this.root_ = current;
};


/**
 * Performs a preorder traversal of the tree.
 *
 * @param {function(goog.structs.SplayTree.Node)} f Visitor function.
 * @private
 */
goog.structs.SplayTree.prototype.traverse_ = function(f) {
  var nodesToVisit = [this.root_];
  while (nodesToVisit.length > 0) {
    var node = nodesToVisit.shift();
    if (node == null) {
      continue;
    }
    f(node);
    nodesToVisit.push(node.left);
    nodesToVisit.push(node.right);
  }
};


/**
 * Constructs a Splay tree node.
 *
 * @param {number} key Key.
 * @param {*} value Value.
 */
goog.structs.SplayTree.Node = function(key, value) {
  this.key = key;
  this.value = value;
};


/**
 * @type {goog.structs.SplayTree.Node}
 */
goog.structs.SplayTree.Node.prototype.left = null;


/**
 * @type {goog.structs.SplayTree.Node}
 */
goog.structs.SplayTree.Node.prototype.right = null;

/* profiler_processor.js */

// Copyright (c) 2009 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/**
 * @fileoverview Profiler processor is used to process log file produced
 * by V8 and produce an internal profile representation which is used
 * for building profile views in 'Profiles' tab.
 */
goog.provide('devtools.profiler.Processor');


/**
 * Creates a Profile View builder object compatible with WebKit Profiler UI.
 *
 * @param {number} samplingRate Number of ms between profiler ticks.
 * @constructor
 */
devtools.profiler.WebKitViewBuilder = function(samplingRate) {
  devtools.profiler.ViewBuilder.call(this, samplingRate);
};
goog.inherits(devtools.profiler.WebKitViewBuilder,
    devtools.profiler.ViewBuilder);


/**
 * @override
 */
devtools.profiler.WebKitViewBuilder.prototype.createViewNode = function(
    funcName, totalTime, selfTime, head) {
  return new devtools.profiler.WebKitViewNode(
      funcName, totalTime, selfTime, head);
};


/**
 * Constructs a Profile View node object for displaying in WebKit Profiler UI.
 *
 * @param {string} internalFuncName A fully qualified function name.
 * @param {number} totalTime Amount of time that application spent in the
 *     corresponding function and its descendants (not that depending on
 *     profile they can be either callees or callers.)
 * @param {number} selfTime Amount of time that application spent in the
 *     corresponding function only.
 * @param {devtools.profiler.ProfileView.Node} head Profile view head.
 * @constructor
 */
devtools.profiler.WebKitViewNode = function(
    internalFuncName, totalTime, selfTime, head) {
  devtools.profiler.ProfileView.Node.call(this,
      internalFuncName, totalTime, selfTime, head);
  this.initFuncInfo_();
  this.callUID = internalFuncName;
};
goog.inherits(devtools.profiler.WebKitViewNode,
    devtools.profiler.ProfileView.Node);


/**
 * RegEx for stripping V8's prefixes of compiled functions.
 */
devtools.profiler.WebKitViewNode.FUNC_NAME_STRIP_RE =
    /^(?:LazyCompile|Function): (.*)$/;


/**
 * RegEx for extracting script source URL and line number.
 */
devtools.profiler.WebKitViewNode.FUNC_NAME_PARSE_RE =
    /^([^ ]+) (.*):(\d+)( \{\d+\})?$/;


/**
 * Inits 'functionName', 'url', and 'lineNumber' fields using 'internalFuncName'
 * field.
 * @private
 */
devtools.profiler.WebKitViewNode.prototype.initFuncInfo_ = function() {
  var nodeAlias = devtools.profiler.WebKitViewNode;
  this.functionName = this.internalFuncName;

  var strippedName = nodeAlias.FUNC_NAME_STRIP_RE.exec(this.functionName);
  if (strippedName) {
    this.functionName = strippedName[1];
  }

  var parsedName = nodeAlias.FUNC_NAME_PARSE_RE.exec(this.functionName);
  if (parsedName) {
    this.functionName = parsedName[1];
    if (parsedName[4]) {
      this.functionName += parsedName[4];
    }
    this.url = parsedName[2];
    this.lineNumber = parsedName[3];
  } else {
    this.url = '';
    this.lineNumber = 0;
  }
};


/**
 * Ancestor of a profile object that leaves out only JS-related functions.
 * @constructor
 */
devtools.profiler.JsProfile = function() {
  devtools.profiler.Profile.call(this);
};
goog.inherits(devtools.profiler.JsProfile, devtools.profiler.Profile);


/**
 * RegExp that leaves only JS functions.
 * @type {RegExp}
 */
devtools.profiler.JsProfile.JS_FUNC_RE = /^(LazyCompile|Function|Script):/;

/**
 * RegExp that filters out native code (ending with "native src.js:xxx").
 * @type {RegExp}
 */
devtools.profiler.JsProfile.JS_NATIVE_FUNC_RE = /\ native\ \w+\.js:\d+$/;

/**
 * RegExp that filters out native scripts.
 * @type {RegExp}
 */
devtools.profiler.JsProfile.JS_NATIVE_SCRIPT_RE = /^Script:\ native/;


/**
 * @override
 */
devtools.profiler.JsProfile.prototype.skipThisFunction = function(name) {
  return !devtools.profiler.JsProfile.JS_FUNC_RE.test(name) ||
      // To profile V8's natives comment out two lines below and '||' above.
      devtools.profiler.JsProfile.JS_NATIVE_FUNC_RE.test(name) ||
      devtools.profiler.JsProfile.JS_NATIVE_SCRIPT_RE.test(name);
};


/**
 * Profiler processor. Consumes profiler log and builds profile views.
 *
 * @param {function(devtools.profiler.ProfileView)} newProfileCallback Callback
 *     that receives a new processed profile.
 * @constructor
 */
devtools.profiler.Processor = function() {
  devtools.profiler.LogReader.call(this, {
      'code-creation': {
          parsers: [null, this.createAddressParser('code'), parseInt, null],
          processor: this.processCodeCreation_, backrefs: true,
          needsProfile: true },
      'code-move': { parsers: [this.createAddressParser('code'),
          this.createAddressParser('code-move-to')],
          processor: this.processCodeMove_, backrefs: true,
          needsProfile: true },
      'code-delete': { parsers: [this.createAddressParser('code')],
          processor: this.processCodeDelete_, backrefs: true,
          needsProfile: true },
      'tick': { parsers: [this.createAddressParser('code'),
          this.createAddressParser('stack'), parseInt, 'var-args'],
          processor: this.processTick_, backrefs: true, needProfile: true },
      'profiler': { parsers: [null, 'var-args'],
          processor: this.processProfiler_, needsProfile: false },
      'heap-sample-begin': { parsers: [null, null, parseInt],
          processor: this.processHeapSampleBegin_ },
      'heap-sample-stats': { parsers: [null, null, parseInt, parseInt],
          processor: this.processHeapSampleStats_ },
      'heap-sample-item': { parsers: [null, parseInt, parseInt],
          processor: this.processHeapSampleItem_ },
      'heap-js-cons-item': { parsers: [null, parseInt, parseInt],
          processor: this.processHeapJsConsItem_ },
      'heap-js-ret-item': { parsers: [null, 'var-args'],
          processor: this.processHeapJsRetItem_ },
      'heap-sample-end': { parsers: [null, null],
          processor: this.processHeapSampleEnd_ },
      // Not used in DevTools Profiler.
      'shared-library': null,
      // Obsolete row types.
      'code-allocate': null,
      'begin-code-region': null,
      'end-code-region': null});


  /**
   * Callback that is called when a new profile is encountered in the log.
   * @type {function()}
   */
  this.startedProfileProcessing_ = null;

  /**
   * Callback that is called periodically to display processing status.
   * @type {function()}
   */
  this.profileProcessingStatus_ = null;

  /**
   * Callback that is called when a profile has been processed and is ready
   * to be shown.
   * @type {function(devtools.profiler.ProfileView)}
   */
  this.finishedProfileProcessing_ = null;

  /**
   * The current profile.
   * @type {devtools.profiler.JsProfile}
   */
  this.currentProfile_ = null;

  /**
   * Builder of profile views. Created during "profiler,begin" event processing.
   * @type {devtools.profiler.WebKitViewBuilder}
   */
  this.viewBuilder_ = null;

  /**
   * Next profile id.
   * @type {number}
   */
  this.profileId_ = 1;

  /**
   * Counter for processed ticks.
   * @type {number}
   */
  this.ticksCount_ = 0;

  /**
   * The current heap snapshot.
   * @type {string}
   */
  this.currentHeapSnapshot_ = null;

  /**
   * Next heap snapshot id.
   * @type {number}
   */
  this.heapSnapshotId_ = 1;
};
goog.inherits(devtools.profiler.Processor, devtools.profiler.LogReader);


/**
 * @override
 */
devtools.profiler.Processor.prototype.printError = function(str) {
  debugPrint(str);
};


/**
 * @override
 */
devtools.profiler.Processor.prototype.skipDispatch = function(dispatch) {
  return dispatch.needsProfile && this.currentProfile_ == null;
};


/**
 * Sets profile processing callbacks.
 *
 * @param {function()} started Started processing callback.
 * @param {function(devtools.profiler.ProfileView)} finished Finished
 *     processing callback.
 */
devtools.profiler.Processor.prototype.setCallbacks = function(
    started, processing, finished) {
  this.startedProfileProcessing_ = started;
  this.profileProcessingStatus_ = processing;
  this.finishedProfileProcessing_ = finished;
};


/**
 * An address for the fake "(program)" entry. WebKit's visualisation
 * has assumptions on how the top of the call tree should look like,
 * and we need to add a fake entry as the topmost function. This
 * address is chosen because it's the end address of the first memory
 * page, which is never used for code or data, but only as a guard
 * page for catching AV errors.
 *
 * @type {number}
 */
devtools.profiler.Processor.PROGRAM_ENTRY = 0xffff;
/**
 * @type {string}
 */
devtools.profiler.Processor.PROGRAM_ENTRY_STR = '0xffff';


/**
 * Sets new profile callback.
 * @param {function(devtools.profiler.ProfileView)} callback Callback function.
 */
devtools.profiler.Processor.prototype.setNewProfileCallback = function(
    callback) {
  this.newProfileCallback_ = callback;
};


devtools.profiler.Processor.prototype.processProfiler_ = function(
    state, params) {
  var processingInterval = null;
  switch (state) {
    case 'resume':
      if (this.currentProfile_ == null) {
        this.currentProfile_ = new devtools.profiler.JsProfile();
        // see the comment for devtools.profiler.Processor.PROGRAM_ENTRY
        this.currentProfile_.addCode(
          'Function', '(program)',
          devtools.profiler.Processor.PROGRAM_ENTRY, 1);
        if (this.startedProfileProcessing_) {
          this.startedProfileProcessing_();
        }
        this.ticksCount_ = 0;
        var self = this;
        if (this.profileProcessingStatus_) {
          processingInterval = window.setInterval(
              function() { self.profileProcessingStatus_(self.ticksCount_); },
              1000);
        }
      }
      break;
    case 'pause':
      if (this.currentProfile_ != null) {
        window.clearInterval(processingInterval);
        if (this.finishedProfileProcessing_) {
          this.finishedProfileProcessing_(this.createProfileForView());
        }
        this.currentProfile_ = null;
      }
      break;
    case 'begin':
      var samplingRate = NaN;
      if (params.length > 0) {
        samplingRate = parseInt(params[0]);
      }
      if (isNaN(samplingRate)) {
        samplingRate = 1;
      }
      this.viewBuilder_ = new devtools.profiler.WebKitViewBuilder(samplingRate);
      break;
    // These events are valid but aren't used.
    case 'compression':
    case 'end': break;
    default:
      throw new Error('unknown profiler state: ' + state);
  }
};


devtools.profiler.Processor.prototype.processCodeCreation_ = function(
    type, start, size, name) {
  this.currentProfile_.addCode(this.expandAlias(type), name, start, size);
};


devtools.profiler.Processor.prototype.processCodeMove_ = function(from, to) {
  this.currentProfile_.moveCode(from, to);
};


devtools.profiler.Processor.prototype.processCodeDelete_ = function(start) {
  this.currentProfile_.deleteCode(start);
};


devtools.profiler.Processor.prototype.processTick_ = function(
    pc, sp, vmState, stack) {
  // see the comment for devtools.profiler.Processor.PROGRAM_ENTRY
  stack.push(devtools.profiler.Processor.PROGRAM_ENTRY_STR);
  this.currentProfile_.recordTick(this.processStack(pc, stack));
  this.ticksCount_++;
};


devtools.profiler.Processor.prototype.processHeapSampleBegin_ = function(
    space, state, ticks) {
  if (space != 'Heap') return;
  this.currentHeapSnapshot_ = {
      number: this.heapSnapshotId_++,
      entries: {},
      clusters: {},
      lowlevels: {},
      ticks: ticks
  };
};


devtools.profiler.Processor.prototype.processHeapSampleStats_ = function(
    space, state, capacity, used) {
  if (space != 'Heap') return;
  this.currentHeapSnapshot_.capacity = capacity;
  this.currentHeapSnapshot_.used = used;
};


devtools.profiler.Processor.prototype.processHeapSampleItem_ = function(
    item, number, size) {
  if (!this.currentHeapSnapshot_) return;
  this.currentHeapSnapshot_.lowlevels[item] = {
    type: item, count: number, size: size
  };
};


devtools.profiler.Processor.prototype.processHeapJsConsItem_ = function(
    item, number, size) {
  if (!this.currentHeapSnapshot_) return;
  this.currentHeapSnapshot_.entries[item] = {
    cons: item, count: number, size: size, retainers: {}
  };
};


devtools.profiler.Processor.prototype.processHeapJsRetItem_ = function(
    item, retainersArray) {
  if (!this.currentHeapSnapshot_) return;
  var rawRetainers = {};
  for (var i = 0, n = retainersArray.length; i < n; ++i) {
    var entry = retainersArray[i].split(';');
    rawRetainers[entry[0]] = parseInt(entry[1], 10);
  }

  function mergeRetainers(entry) {
    for (var rawRetainer in rawRetainers) {
      var consName = rawRetainer.indexOf(':') != -1 ?
          rawRetainer.split(':')[0] : rawRetainer;
      if (!(consName in entry.retainers))
        entry.retainers[consName] = { cons: consName, count: 0, clusters: {} };
      var retainer = entry.retainers[consName];
      retainer.count += rawRetainers[rawRetainer];
      if (consName != rawRetainer)
        retainer.clusters[rawRetainer] = true;
    }
  }

  if (item.indexOf(':') != -1) {
    // Array, Function, or Object instances cluster case.
    if (!(item in this.currentHeapSnapshot_.clusters)) {
      this.currentHeapSnapshot_.clusters[item] = {
        cons: item, retainers: {}
      };
    }
    mergeRetainers(this.currentHeapSnapshot_.clusters[item]);
    item = item.split(':')[0];
  }
  mergeRetainers(this.currentHeapSnapshot_.entries[item]);
};


devtools.profiler.Processor.prototype.processHeapSampleEnd_ = function(
    space, state) {
  if (space != 'Heap') return;
  var snapshot = this.currentHeapSnapshot_;
  this.currentHeapSnapshot_ = null;
  // For some reason, 'used' from 'heap-sample-stats' sometimes differ from
  // the sum of objects sizes. To avoid discrepancy, we re-calculate 'used'.
  snapshot.used = 0;
  for (var item in snapshot.lowlevels) {
      snapshot.used += snapshot.lowlevels[item].size;
  }
  WebInspector.panels.profiles.addSnapshot(snapshot);
};


/**
 * Creates a profile for further displaying in ProfileView.
 */
devtools.profiler.Processor.prototype.createProfileForView = function() {
  var profile = this.viewBuilder_.buildView(
      this.currentProfile_.getTopDownProfile());
  profile.uid = this.profileId_++;
  profile.title = UserInitiatedProfileName + '.' + profile.uid;
  return profile;
};

/* heap_profiler_panel.js */

// Copyright (c) 2009 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/**
 * @fileoverview Heap profiler panel implementation.
 */

WebInspector.ProfilesPanel.prototype.addSnapshot = function(snapshot) {
    snapshot.title = WebInspector.UIString("Snapshot %d", snapshot.number);
    snapshot.typeId = WebInspector.HeapSnapshotProfileType.TypeId;

    var snapshots = WebInspector.HeapSnapshotProfileType.snapshots;
    snapshots.push(snapshot);

    snapshot.listIndex = snapshots.length - 1;

    if (WebInspector.CPUProfile)
        this.addProfileHeader(WebInspector.HeapSnapshotProfileType.TypeId, snapshot);
    else
        this.addProfileHeader(snapshot);

    this.dispatchEventToListeners("snapshot added");
}


WebInspector.HeapSnapshotView = function(parent, profile)
{
    WebInspector.View.call(this);

    this.element.addStyleClass("heap-snapshot-view");

    this.parent = parent;
    this.parent.addEventListener("snapshot added", this._updateBaseOptions, this);

    this.showCountAsPercent = false;
    this.showSizeAsPercent = false;
    this.showCountDeltaAsPercent = false;
    this.showSizeDeltaAsPercent = false;

    this.categories = {
        code: new WebInspector.ResourceCategory("code", WebInspector.UIString("Code"), "rgb(255,121,0)"),
        data: new WebInspector.ResourceCategory("data", WebInspector.UIString("Objects and Data"), "rgb(47,102,236)"),
        other: new WebInspector.ResourceCategory("other", WebInspector.UIString("Other"), "rgb(186,186,186)")
    };

    this.summaryBar = new WebInspector.SummaryBar(this.categories);
    this.summaryBar.element.id = "heap-snapshot-summary";
    this.summaryBar.calculator = new WebInspector.HeapSummaryCalculator(profile.used);
    this.element.appendChild(this.summaryBar.element);

    var columns = { "cons": { title: WebInspector.UIString("Constructor"), disclosure: true, sortable: true },
                    "count": { title: WebInspector.UIString("Count"), width: "54px", sortable: true },
                    "size": { title: WebInspector.UIString("Size"), width: "72px", sort: "descending", sortable: true },
                    "countDelta": { title: WebInspector.UIString("\xb1 Count"), width: "72px", sortable: true },
                    "sizeDelta": { title: WebInspector.UIString("\xb1 Size"), width: "72px", sortable: true } };

    this.dataGrid = new WebInspector.DataGrid(columns);
    this.dataGrid.addEventListener("sorting changed", this._sortData, this);
    this.dataGrid.element.addEventListener("mousedown", this._mouseDownInDataGrid.bind(this), true);
    this.element.appendChild(this.dataGrid.element);

    this.profile = profile;

    this.baseSelectElement = document.createElement("select");
    this.baseSelectElement.className = "status-bar-item";
    this.baseSelectElement.addEventListener("change", this._changeBase.bind(this), false);
    this._updateBaseOptions();
    if (this.profile.listIndex > 0)
        this.baseSelectElement.selectedIndex = this.profile.listIndex - 1;
    else
        this.baseSelectElement.selectedIndex = this.profile.listIndex;
    this._resetDataGridList();

    this.percentButton = new WebInspector.StatusBarButton("", "percent-time-status-bar-item status-bar-item");
    this.percentButton.addEventListener("click", this._percentClicked.bind(this), false);

    this.refresh();

    this._updatePercentButton();
};

WebInspector.HeapSnapshotView.prototype = {

    get statusBarItems()
    {
        return [this.baseSelectElement, this.percentButton.element];
    },

    get profile()
    {
        return this._profile;
    },

    set profile(profile)
    {
        this._profile = profile;
    },

    show: function(parentElement)
    {
        WebInspector.View.prototype.show.call(this, parentElement);
        this.dataGrid.updateWidths();
    },

    hide: function()
    {
        WebInspector.View.prototype.hide.call(this);
        this._currentSearchResultIndex = -1;
    },

    resize: function()
    {
        if (this.dataGrid)
            this.dataGrid.updateWidths();
    },

    refresh: function()
    {
        this.dataGrid.removeChildren();

        var children = this.snapshotDataGridList.children;
        var count = children.length;
        for (var index = 0; index < count; ++index)
            this.dataGrid.appendChild(children[index]);

        this._updateSummaryGraph();
    },

    refreshShowAsPercents: function()
    {
        this._updatePercentButton();
        this.refreshVisibleData();
    },

    _deleteSearchMatchedFlags: function(node)
    {
        delete node._searchMatchedConsColumn;
        delete node._searchMatchedCountColumn;
        delete node._searchMatchedSizeColumn;
        delete node._searchMatchedCountDeltaColumn;
        delete node._searchMatchedSizeDeltaColumn;
    },

    searchCanceled: function()
    {
        if (this._searchResults) {
            for (var i = 0; i < this._searchResults.length; ++i) {
                var profileNode = this._searchResults[i].profileNode;
                this._deleteSearchMatchedFlags(profileNode);
                profileNode.refresh();
            }
        }

        delete this._searchFinishedCallback;
        this._currentSearchResultIndex = -1;
        this._searchResults = [];
    },

    performSearch: function(query, finishedCallback)
    {
        // Call searchCanceled since it will reset everything we need before doing a new search.
        this.searchCanceled();

        query = query.trimWhitespace();

        if (!query.length)
            return;

        this._searchFinishedCallback = finishedCallback;

        var helper = WebInspector.HeapSnapshotView.SearchHelper;

        var operationAndNumber = helper.parseOperationAndNumber(query);
        var operation = operationAndNumber[0];
        var queryNumber = operationAndNumber[1];

        var percentUnits = helper.percents.test(query);
        var megaBytesUnits = helper.megaBytes.test(query);
        var kiloBytesUnits = helper.kiloBytes.test(query);
        var bytesUnits = helper.bytes.test(query);

        var queryNumberBytes = (megaBytesUnits ? (queryNumber * 1024 * 1024) : (kiloBytesUnits ? (queryNumber * 1024) : queryNumber));

        function matchesQuery(heapSnapshotDataGridNode)
        {
            WebInspector.HeapSnapshotView.prototype._deleteSearchMatchedFlags(heapSnapshotDataGridNode);

            if (percentUnits) {
                heapSnapshotDataGridNode._searchMatchedCountColumn = operation(heapSnapshotDataGridNode.countPercent, queryNumber);
                heapSnapshotDataGridNode._searchMatchedSizeColumn = operation(heapSnapshotDataGridNode.sizePercent, queryNumber);
                heapSnapshotDataGridNode._searchMatchedCountDeltaColumn = operation(heapSnapshotDataGridNode.countDeltaPercent, queryNumber);
                heapSnapshotDataGridNode._searchMatchedSizeDeltaColumn = operation(heapSnapshotDataGridNode.sizeDeltaPercent, queryNumber);
            } else if (megaBytesUnits || kiloBytesUnits || bytesUnits) {
                heapSnapshotDataGridNode._searchMatchedSizeColumn = operation(heapSnapshotDataGridNode.size, queryNumberBytes);
                heapSnapshotDataGridNode._searchMatchedSizeDeltaColumn = operation(heapSnapshotDataGridNode.sizeDelta, queryNumberBytes);
            } else {
                heapSnapshotDataGridNode._searchMatchedCountColumn = operation(heapSnapshotDataGridNode.count, queryNumber);
                heapSnapshotDataGridNode._searchMatchedCountDeltaColumn = operation(heapSnapshotDataGridNode.countDelta, queryNumber);
            }

            if (heapSnapshotDataGridNode.constructorName.hasSubstring(query, true))
                heapSnapshotDataGridNode._searchMatchedConsColumn = true;

            if (heapSnapshotDataGridNode._searchMatchedConsColumn ||
                heapSnapshotDataGridNode._searchMatchedCountColumn ||
                heapSnapshotDataGridNode._searchMatchedSizeColumn ||
                heapSnapshotDataGridNode._searchMatchedCountDeltaColumn ||
                heapSnapshotDataGridNode._searchMatchedSizeDeltaColumn) {
                heapSnapshotDataGridNode.refresh();
                return true;
            }

            return false;
        }

        var current = this.snapshotDataGridList.children[0];
        var depth = 0;
        var info = {};

        // The second and subsequent levels of heap snapshot nodes represent retainers,
        // so recursive expansion will be infinite, since a graph is being traversed.
        // So default to a recursion cap of 2 levels.
        var maxDepth = 2;

        while (current) {
            if (matchesQuery(current))
                this._searchResults.push({ profileNode: current });
            current = current.traverseNextNode(false, null, (depth >= maxDepth), info);
            depth += info.depthChange;
        }

        finishedCallback(this, this._searchResults.length);
    },

    jumpToFirstSearchResult: WebInspector.CPUProfileView.prototype.jumpToFirstSearchResult,
    jumpToLastSearchResult: WebInspector.CPUProfileView.prototype.jumpToLastSearchResult,
    jumpToNextSearchResult: WebInspector.CPUProfileView.prototype.jumpToNextSearchResult,
    jumpToPreviousSearchResult: WebInspector.CPUProfileView.prototype.jumpToPreviousSearchResult,
    showingFirstSearchResult: WebInspector.CPUProfileView.prototype.showingFirstSearchResult,
    showingLastSearchResult: WebInspector.CPUProfileView.prototype.showingLastSearchResult,
    _jumpToSearchResult: WebInspector.CPUProfileView.prototype._jumpToSearchResult,

    refreshVisibleData: function()
    {
        var child = this.dataGrid.children[0];
        while (child) {
            child.refresh();
            child = child.traverseNextNode(false, null, true);
        }
        this._updateSummaryGraph();
    },

    _changeBase: function() {
        if (this.baseSnapshot === WebInspector.HeapSnapshotProfileType.snapshots[this.baseSelectElement.selectedIndex])
            return;

        this._resetDataGridList();
        this.refresh();

        if (!this.currentQuery || !this._searchFinishedCallback || !this._searchResults)
            return;

        // The current search needs to be performed again. First negate out previous match
        // count by calling the search finished callback with a negative number of matches.
        // Then perform the search again with the same query and callback.
        this._searchFinishedCallback(this, -this._searchResults.length);
        this.performSearch(this.currentQuery, this._searchFinishedCallback);
    },

    _createSnapshotDataGridList: function()
    {
        if (this._snapshotDataGridList)
          delete this._snapshotDataGridList;

        this._snapshotDataGridList = new WebInspector.HeapSnapshotDataGridList(this, this.baseSnapshot.entries, this.profile.entries);
        return this._snapshotDataGridList;
    },

    _mouseDownInDataGrid: function(event)
    {
        if (event.detail < 2)
            return;

        var cell = event.target.enclosingNodeOrSelfWithNodeName("td");
        if (!cell || (!cell.hasStyleClass("count-column") && !cell.hasStyleClass("size-column") && !cell.hasStyleClass("countDelta-column") && !cell.hasStyleClass("sizeDelta-column")))
            return;

        if (cell.hasStyleClass("count-column"))
            this.showCountAsPercent = !this.showCountAsPercent;
        else if (cell.hasStyleClass("size-column"))
            this.showSizeAsPercent = !this.showSizeAsPercent;
        else if (cell.hasStyleClass("countDelta-column"))
            this.showCountDeltaAsPercent = !this.showCountDeltaAsPercent;
        else if (cell.hasStyleClass("sizeDelta-column"))
            this.showSizeDeltaAsPercent = !this.showSizeDeltaAsPercent;

        this.refreshShowAsPercents();

        event.preventDefault();
        event.stopPropagation();
    },

    get _isShowingAsPercent()
    {
        return this.showCountAsPercent && this.showSizeAsPercent && this.showCountDeltaAsPercent && this.showSizeDeltaAsPercent;
    },

    _percentClicked: function(event)
    {
        var currentState = this._isShowingAsPercent;
        this.showCountAsPercent = !currentState;
        this.showSizeAsPercent = !currentState;
        this.showCountDeltaAsPercent = !currentState;
        this.showSizeDeltaAsPercent = !currentState;
        this.refreshShowAsPercents();
    },

    _resetDataGridList: function()
    {
        this.baseSnapshot = WebInspector.HeapSnapshotProfileType.snapshots[this.baseSelectElement.selectedIndex];
        var lastComparator = WebInspector.HeapSnapshotDataGridList.propertyComparator("size", false);
        if (this.snapshotDataGridList)
            lastComparator = this.snapshotDataGridList.lastComparator;
        this.snapshotDataGridList = this._createSnapshotDataGridList();
        this.snapshotDataGridList.sort(lastComparator, true);
    },

    _sortData: function()
    {
        var sortAscending = this.dataGrid.sortOrder === "ascending";
        var sortColumnIdentifier = this.dataGrid.sortColumnIdentifier;
        var sortProperty = {
                "cons": ["constructorName", null],
                "count": ["count", null],
                "size": ["size", "count"],
                "countDelta": this.showCountDeltaAsPercent ? ["countDeltaPercent", null] : ["countDelta", null],
                "sizeDelta": this.showSizeDeltaAsPercent ? ["sizeDeltaPercent", "countDeltaPercent"] : ["sizeDelta", "sizeDeltaPercent"]
        }[sortColumnIdentifier];

        this.snapshotDataGridList.sort(WebInspector.HeapSnapshotDataGridList.propertyComparator(sortProperty[0], sortProperty[1], sortAscending));

        this.refresh();
    },

    _updateBaseOptions: function()
    {
        var list = WebInspector.HeapSnapshotProfileType.snapshots;
        // We're assuming that snapshots can only be added.
        if (this.baseSelectElement.length == list.length)
            return;

        for (var i = this.baseSelectElement.length, n = list.length; i < n; ++i) {
            var baseOption = document.createElement("option");
            baseOption.label = WebInspector.UIString("Compared to %s", list[i].title);
            this.baseSelectElement.appendChild(baseOption);
        }
    },

    _updatePercentButton: function()
    {
        if (this._isShowingAsPercent) {
            this.percentButton.title = WebInspector.UIString("Show absolute counts and sizes.");
            this.percentButton.toggled = true;
        } else {
            this.percentButton.title = WebInspector.UIString("Show counts and sizes as percentages.");
            this.percentButton.toggled = false;
        }
    },

    _updateSummaryGraph: function()
    {
        this.summaryBar.calculator.showAsPercent = this._isShowingAsPercent;
        this.summaryBar.update(this.profile.lowlevels);
    }
};

WebInspector.HeapSnapshotView.prototype.__proto__ = WebInspector.View.prototype;

WebInspector.HeapSnapshotView.SearchHelper = {
    // In comparators, we assume that a value from a node is passed as the first parameter.
    operations: { LESS: function (a, b) { return a !== null && a < b; },
                  LESS_OR_EQUAL: function (a, b) { return a !== null && a <= b; },
                  EQUAL: function (a, b) { return a !== null && a == b; },
                  GREATER_OR_EQUAL: function (a, b) { return a !== null && a >= b; },
                  GREATER: function (a, b) { return a !== null && a > b; } },

    operationParsers: { LESS: /^<(\d+)/,
                        LESS_OR_EQUAL: /^<=(\d+)/,
                        GREATER_OR_EQUAL: /^>=(\d+)/,
                        GREATER: /^>(\d+)/ },

    parseOperationAndNumber: function(query) 
    {
        var operations = WebInspector.HeapSnapshotView.SearchHelper.operations;
        var parsers = WebInspector.HeapSnapshotView.SearchHelper.operationParsers;
        for (var operation in parsers) {
            var match = query.match(parsers[operation]);
            if (match != null)
                return [operations[operation], parseFloat(match[1])];
        }
        return [operations.EQUAL, parseFloat(query)];
    },

    percents: /%$/,

    megaBytes: /MB$/i,

    kiloBytes: /KB$/i,

    bytes: /B$/i
}

WebInspector.HeapSummaryCalculator = function(total)
{
    this.total = total;
}

WebInspector.HeapSummaryCalculator.prototype = {
    computeSummaryValues: function(lowLevels)
    {
        function highFromLow(type) {
            if (type == "CODE_TYPE" || type == "SHARED_FUNCTION_INFO_TYPE" || type == "SCRIPT_TYPE") return "code";
            if (type == "STRING_TYPE" || type == "HEAP_NUMBER_TYPE" || type.match(/^JS_/) || type.match(/_ARRAY_TYPE$/)) return "data";
            return "other";
        }
        var highLevels = {data: 0, code: 0, other: 0};
        for (var item in lowLevels) {
            var highItem = highFromLow(item);
            highLevels[highItem] += lowLevels[item].size;
        }
        return {categoryValues: highLevels};
    },

    formatValue: function(value)
    {
        if (this.showAsPercent)
            return WebInspector.UIString("%.2f%%", value / this.total * 100.0);
        else
            return Number.bytesToString(value);
    },

    get showAsPercent()
    {
        return this._showAsPercent;
    },

    set showAsPercent(x)
    {
        this._showAsPercent = x;
    }
}

WebInspector.HeapSnapshotSidebarTreeElement = function(snapshot)
{
    this.profile = snapshot;

    WebInspector.SidebarTreeElement.call(this, "heap-snapshot-sidebar-tree-item", "", "", snapshot, false);

    this.refreshTitles();
};

WebInspector.HeapSnapshotSidebarTreeElement.prototype = {
    get mainTitle()
    {
        if (this._mainTitle)
            return this._mainTitle;
        return this.profile.title;
    },

    set mainTitle(x)
    {
        this._mainTitle = x;
        this.refreshTitles();
    },

    get subtitle()
    {
        if (this._subTitle)
            return this._subTitle;
        return WebInspector.UIString("Used %s of %s (%.0f%%)", Number.bytesToString(this.profile.used, null, false), Number.bytesToString(this.profile.capacity, null, false), this.profile.used / this.profile.capacity * 100.0);
    },

    set subtitle(x)
    {
        this._subTitle = x;
        this.refreshTitles();
    }
};

WebInspector.HeapSnapshotSidebarTreeElement.prototype.__proto__ = WebInspector.ProfileSidebarTreeElement.prototype;

WebInspector.HeapSnapshotDataGridNodeWithRetainers = function(owningTree)
{
    this.tree = owningTree;

    WebInspector.DataGridNode.call(this, null, this._hasRetainers);

    this.addEventListener("populate", this._populate, this);
};

WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype = {
    isEmptySet: function(set)
    {
        for (var x in set)
            return false;
        return true;
    },

    get _hasRetainers()
    {
        return !this.isEmptySet(this.retainers);
    },

    get _parent()
    {
        // For top-level nodes, return owning tree as a parent, not data grid.
        return this.parent !== this.dataGrid ? this.parent : this.tree;
    },

    _populate: function(event)
    {
        var self = this;
        this.produceDiff(this.baseRetainers, this.retainers, function(baseItem, snapshotItem) {
            self.appendChild(new WebInspector.HeapSnapshotDataGridRetainerNode(self.snapshotView, baseItem, snapshotItem, self.tree));
        });

        if (this._parent) {
            var currentComparator = this._parent.lastComparator;
            if (currentComparator)
                this.sort(currentComparator, true);
        }

        this.removeEventListener("populate", this._populate, this);
    },

    produceDiff: function(baseEntries, currentEntries, callback)
    {
        for (var item in currentEntries)
            callback(baseEntries[item], currentEntries[item]);

        for (item in baseEntries) {
            if (!(item in currentEntries))
                callback(baseEntries[item], null);
        }
    },

    sort: function(comparator, force) {
        if (!force && this.lastComparator === comparator)
            return;

        this.children.sort(comparator);
        var childCount = this.children.length;
        for (var childIndex = 0; childIndex < childCount; ++childIndex)
            this.children[childIndex]._recalculateSiblings(childIndex);
        for (var i = 0; i < this.children.length; ++i) {
            var child = this.children[i];
            if (!force && (!child.expanded || child.lastComparator === comparator))
                continue;
            child.sort(comparator, force);
        }
        this.lastComparator = comparator;
    },

    signForDelta: function(delta) {
        if (delta == 0)
            return "";
        if (delta > 0)
            return "+";
        else
            // Math minus sign, same width as plus.
            return "\u2212";
    },

    showDeltaAsPercent: function(value) {
        if (value === Number.POSITIVE_INFINITY)
            return WebInspector.UIString("new");
        else if (value === Number.NEGATIVE_INFINITY)
            return WebInspector.UIString("deleted");
        if (value > 1000.0)
            return WebInspector.UIString("%s >1000%%", this.signForDelta(value));
        return WebInspector.UIString("%s%.2f%%", this.signForDelta(value), Math.abs(value));
    },

    getTotalCount: function() {
        if (!this._count) {
            this._count = 0;
            for (var i = 0, n = this.children.length; i < n; ++i)
                this._count += this.children[i].count;
        }
        return this._count;
    },

    getTotalSize: function() {
        if (!this._size) {
            this._size = 0;
            for (var i = 0, n = this.children.length; i < n; ++i)
                this._size += this.children[i].size;
        }
        return this._size;
    },

    get countPercent()
    {
        return this.count / this._parent.getTotalCount() * 100.0;
    },

    get sizePercent()
    {
        return this.size / this._parent.getTotalSize() * 100.0;
    },

    get countDeltaPercent()
    {
        if (this.baseCount > 0) {
            if (this.count > 0)
                return this.countDelta / this.baseCount * 100.0;
            else
                return Number.NEGATIVE_INFINITY;
        } else
            return Number.POSITIVE_INFINITY;
    },

    get sizeDeltaPercent()
    {
        if (this.baseSize > 0) {
            if (this.size > 0)
                return this.sizeDelta / this.baseSize * 100.0;
            else
                return Number.NEGATIVE_INFINITY;
        } else
            return Number.POSITIVE_INFINITY;
    },

    get data()
    {
        var data = {};

        data["cons"] = this.constructorName;

        if (this.snapshotView.showCountAsPercent)
            data["count"] = WebInspector.UIString("%.2f%%", this.countPercent);
        else
            data["count"] = this.count;

        if (this.size !== null) {
            if (this.snapshotView.showSizeAsPercent)
                data["size"] = WebInspector.UIString("%.2f%%", this.sizePercent);
            else
                data["size"] = Number.bytesToString(this.size);
        } else
            data["size"] = "";

        if (this.snapshotView.showCountDeltaAsPercent)
            data["countDelta"] = this.showDeltaAsPercent(this.countDeltaPercent);
        else
            data["countDelta"] = WebInspector.UIString("%s%d", this.signForDelta(this.countDelta), Math.abs(this.countDelta));

        if (this.sizeDelta != null) {
            if (this.snapshotView.showSizeDeltaAsPercent)
                data["sizeDelta"] = this.showDeltaAsPercent(this.sizeDeltaPercent);
            else
                data["sizeDelta"] = WebInspector.UIString("%s%s", this.signForDelta(this.sizeDelta), Number.bytesToString(Math.abs(this.sizeDelta)));
        } else
            data["sizeDelta"] = "";

        return data;
    },

    createCell: function(columnIdentifier)
    {
        var cell = WebInspector.DataGridNode.prototype.createCell.call(this, columnIdentifier);

        if ((columnIdentifier === "cons" && this._searchMatchedConsColumn) ||
            (columnIdentifier === "count" && this._searchMatchedCountColumn) ||
            (columnIdentifier === "size" && this._searchMatchedSizeColumn) ||
            (columnIdentifier === "countDelta" && this._searchMatchedCountDeltaColumn) ||
            (columnIdentifier === "sizeDelta" && this._searchMatchedSizeDeltaColumn))
            cell.addStyleClass("highlight");

        return cell;
    }
};

WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype.__proto__ = WebInspector.DataGridNode.prototype;

WebInspector.HeapSnapshotDataGridNode = function(snapshotView, baseEntry, snapshotEntry, owningTree)
{
    this.snapshotView = snapshotView;

    if (!snapshotEntry)
        snapshotEntry = { cons: baseEntry.cons, count: 0, size: 0, retainers: {} };
    this.constructorName = snapshotEntry.cons;
    this.count = snapshotEntry.count;
    this.size = snapshotEntry.size;
    this.retainers = snapshotEntry.retainers;

    if (!baseEntry)
        baseEntry = { count: 0, size: 0, retainers: {} };
    this.baseCount = baseEntry.count;
    this.countDelta = this.count - this.baseCount;
    this.baseSize = baseEntry.size;
    this.sizeDelta = this.size - this.baseSize;
    this.baseRetainers = baseEntry.retainers;

    WebInspector.HeapSnapshotDataGridNodeWithRetainers.call(this, owningTree);
};

WebInspector.HeapSnapshotDataGridNode.prototype.__proto__ = WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype;

WebInspector.HeapSnapshotDataGridList = function(snapshotView, baseEntries, snapshotEntries)
{
    this.tree = this;
    this.snapshotView = snapshotView;
    this.children = [];
    this.lastComparator = null;
    this.populateChildren(baseEntries, snapshotEntries);
};

WebInspector.HeapSnapshotDataGridList.prototype = {
    appendChild: function(child)
    {
        this.insertChild(child, this.children.length);
    },

    insertChild: function(child, index)
    {
        this.children.splice(index, 0, child);
    },

    removeChildren: function()
    {
        this.children = [];
    },

    populateChildren: function(baseEntries, snapshotEntries)
    {
        var self = this;
        this.produceDiff(baseEntries, snapshotEntries, function(baseItem, snapshotItem) {
            self.appendChild(new WebInspector.HeapSnapshotDataGridNode(self.snapshotView, baseItem, snapshotItem, self));
        });
    },

    produceDiff: WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype.produceDiff,
    sort: WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype.sort,
    getTotalCount: WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype.getTotalCount,
    getTotalSize: WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype.getTotalSize
};

WebInspector.HeapSnapshotDataGridList.propertyComparators = [{}, {}];

WebInspector.HeapSnapshotDataGridList.propertyComparator = function(property, property2, isAscending)
{
    var propertyHash = property + '#' + property2;
    var comparator = this.propertyComparators[(isAscending ? 1 : 0)][propertyHash];
    if (!comparator) {
        comparator = function(lhs, rhs) {
            var l = lhs[property], r = rhs[property];
            if ((l === null || r === null) && property2 !== null)
                l = lhs[property2], r = rhs[property2];
            var result = l < r ? -1 : (l > r ? 1 : 0);
            return isAscending ? result : -result;
        };
        this.propertyComparators[(isAscending ? 1 : 0)][propertyHash] = comparator;
    }
    return comparator;
};

WebInspector.HeapSnapshotDataGridRetainerNode = function(snapshotView, baseEntry, snapshotEntry, owningTree)
{
    this.snapshotView = snapshotView;

    if (!snapshotEntry)
        snapshotEntry = { cons: baseEntry.cons, count: 0, clusters: {} };
    this.constructorName = snapshotEntry.cons;
    this.count = snapshotEntry.count;
    this.retainers = this._calculateRetainers(this.snapshotView.profile, snapshotEntry.clusters);

    if (!baseEntry)
        baseEntry = { count: 0, clusters: {} };
    this.baseCount = baseEntry.count;
    this.countDelta = this.count - this.baseCount;
    this.baseRetainers = this._calculateRetainers(this.snapshotView.baseSnapshot, baseEntry.clusters);

    this.size = null;
    this.sizeDelta = null;

    WebInspector.HeapSnapshotDataGridNodeWithRetainers.call(this, owningTree);
}

WebInspector.HeapSnapshotDataGridRetainerNode.prototype = {
    get sizePercent()
    {
        return null;
    },

    get sizeDeltaPercent()
    {
        return null;
    },

    _calculateRetainers: function(snapshot, clusters) {
        var retainers = {};
        if (this.isEmptySet(clusters)) {
            if (this.constructorName in snapshot.entries)
                return snapshot.entries[this.constructorName].retainers;
        } else {
            // In case when an entry is retained by clusters, we need to gather up the list
            // of retainers by merging retainers of every cluster.
            // E.g. having such a tree:
            //   A
            //     Object:1  10
            //       X       3
            //       Y       4
            //     Object:2  5
            //       X       6
            //
            // will result in a following retainers list: X 9, Y 4.
            for (var clusterName in clusters) {
                if (clusterName in snapshot.clusters) {
                    var clusterRetainers = snapshot.clusters[clusterName].retainers;
                    for (var clusterRetainer in clusterRetainers) {
                        var clusterRetainerEntry = clusterRetainers[clusterRetainer];
                        if (!(clusterRetainer in retainers))
                            retainers[clusterRetainer] = { cons: clusterRetainerEntry.cons, count: 0, clusters: {} };
                        retainers[clusterRetainer].count += clusterRetainerEntry.count;
                        for (var clusterRetainerCluster in clusterRetainerEntry.clusters)
                            retainers[clusterRetainer].clusters[clusterRetainerCluster] = true;
                    }
                }
            }
        }
        return retainers;
    }
};

WebInspector.HeapSnapshotDataGridRetainerNode.prototype.__proto__ = WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype;


WebInspector.HeapSnapshotProfileType = function()
{
    WebInspector.ProfileType.call(this, WebInspector.HeapSnapshotProfileType.TypeId, WebInspector.UIString("HEAP SNAPSHOTS"));
}

WebInspector.HeapSnapshotProfileType.TypeId = "HEAP";

WebInspector.HeapSnapshotProfileType.snapshots = [];

WebInspector.HeapSnapshotProfileType.prototype = {
    get buttonTooltip()
    {
        return WebInspector.UIString("Take heap snapshot.");
    },

    get buttonStyle()
    {
        return "heap-snapshot-status-bar-item status-bar-item";
    },

    buttonClicked: function()
    {
        InspectorController.takeHeapSnapshot();
    },

    createSidebarTreeElementForProfile: function(profile)
    {
        var element = new WebInspector.HeapSnapshotSidebarTreeElement(profile);
        element.small = false;
        return element;
    },

    createView: function(profile)
    {
        return new WebInspector.HeapSnapshotView(WebInspector.panels.profiles, profile);
    }
}

WebInspector.HeapSnapshotProfileType.prototype.__proto__ = WebInspector.ProfileType.prototype;


(function() {
    var originalCreatePanels = WebInspector._createPanels;
    WebInspector._createPanels = function() {
        originalCreatePanels.apply(this, arguments);
        if (WebInspector.panels.profiles)
            WebInspector.panels.profiles.registerProfileType(new WebInspector.HeapSnapshotProfileType());
    }
})();

/* devtools.js */

// Copyright (c) 2009 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/**
 * @fileoverview Tools is a main class that wires all components of the
 * DevTools frontend together. It is also responsible for overriding existing
 * WebInspector functionality while it is getting upstreamed into WebCore.
 */
goog.provide('devtools.Tools');

goog.require('devtools.DebuggerAgent');


/**
 * Dispatches raw message from the host.
 * @param {string} remoteName
 * @prama {string} methodName
 * @param {string} param1, param2, param3 Arguments to dispatch.
 */
devtools$$dispatch = function(remoteName, methodName, param1, param2, param3) {
  remoteName = 'Remote' + remoteName.substring(0, remoteName.length - 8);
  var agent = window[remoteName];
  if (!agent) {
    debugPrint('No remote agent "' + remoteName + '" found.');
    return;
  }
  var method = agent[methodName];
  if (!method) {
    debugPrint('No method "' + remoteName + '.' + methodName + '" found.');
    return;
  }
  method.call(this, param1, param2, param3);
};


devtools.ToolsAgent = function() {
  RemoteToolsAgent.DidDispatchOn =
      WebInspector.Callback.processCallback;
  RemoteToolsAgent.FrameNavigate =
      goog.bind(this.frameNavigate_, this);
  RemoteToolsAgent.DispatchOnClient =
      goog.bind(this.dispatchOnClient_, this);
  this.debuggerAgent_ = new devtools.DebuggerAgent();
};


/**
 * Resets tools agent to its initial state.
 */
devtools.ToolsAgent.prototype.reset = function() {
  DevToolsHost.reset();
  this.debuggerAgent_.reset();
};


/**
 * @param {string} script Script exression to be evaluated in the context of the
 *     inspected page.
 * @param {function(Object|string, boolean):undefined} opt_callback Function to
 *     call with the result.
 */
devtools.ToolsAgent.prototype.evaluateJavaScript = function(script,
    opt_callback) {
  InspectorController.evaluate(script, opt_callback || function() {});
};


/**
 * @return {devtools.DebuggerAgent} Debugger agent instance.
 */
devtools.ToolsAgent.prototype.getDebuggerAgent = function() {
  return this.debuggerAgent_;
};


/**
 * @param {string} url Url frame navigated to.
 * @see tools_agent.h
 * @private
 */
devtools.ToolsAgent.prototype.frameNavigate_ = function(url) {
  this.reset();
  // Do not reset Profiles panel.
  var profiles = null;
  if ('profiles' in WebInspector.panels) {
    profiles = WebInspector.panels['profiles'];
    delete WebInspector.panels['profiles'];
  }
  WebInspector.reset();
  if (profiles != null) {
    WebInspector.panels['profiles'] = profiles;
  }
};


/**
 * @param {string} message Serialized call to be dispatched on WebInspector.
 * @private
 */
devtools.ToolsAgent.prototype.dispatchOnClient_ = function(message) {
  var args = JSON.parse(message);
  var methodName = args[0];
  var parameters = args.slice(1);
  WebInspector[methodName].apply(WebInspector, parameters);
};


/**
 * Evaluates js expression.
 * @param {string} expr
 */
devtools.ToolsAgent.prototype.evaluate = function(expr) {
  RemoteToolsAgent.evaluate(expr);
};


/**
 * Enables / disables resources panel in the ui.
 * @param {boolean} enabled New panel status.
 */
WebInspector.setResourcesPanelEnabled = function(enabled) {
  InspectorController._resourceTrackingEnabled = enabled;
  WebInspector.panels.resources.reset();
};


/**
 * Prints string  to the inspector console or shows alert if the console doesn't
 * exist.
 * @param {string} text
 */
function debugPrint(text) {
  var console = WebInspector.console;
  if (console) {
    console.addMessage(new WebInspector.ConsoleMessage(
        WebInspector.ConsoleMessage.MessageSource.JS,
        WebInspector.ConsoleMessage.MessageType.Log,
        WebInspector.ConsoleMessage.MessageLevel.Log,
        1, 'chrome://devtools/<internal>', undefined, -1, text));
  } else {
    alert(text);
  }
}


/**
 * Global instance of the tools agent.
 * @type {devtools.ToolsAgent}
 */
devtools.tools = null;


var context = {};  // Used by WebCore's inspector routines.

///////////////////////////////////////////////////////////////////////////////
// Here and below are overrides to existing WebInspector methods only.
// TODO(pfeldman): Patch WebCore and upstream changes.
var oldLoaded = WebInspector.loaded;
WebInspector.loaded = function() {
  devtools.tools = new devtools.ToolsAgent();
  devtools.tools.reset();

  Preferences.ignoreWhitespace = false;
  Preferences.samplingCPUProfiler = true;
  Preferences.heapProfilerPresent = true;
  oldLoaded.call(this);

  // Hide dock button on Mac OS.
  // TODO(pfeldman): remove once Mac OS docking is implemented.
  if (InspectorController.platform().indexOf('mac') == 0) {
    document.getElementById('dock-status-bar-item').addStyleClass('hidden');
  }

  DevToolsHost.loaded();
};


(function() {

  /**
   * Handles an F3 keydown event to focus the Inspector search box.
   * @param {KeyboardEvent} event Event to optionally handle
   * @return {boolean} whether the event has been handled
   */
  function handleF3Keydown(event) {
    if (event.keyIdentifier == 'F3' && !event.altKey && !event.ctrlKey &&
        !event.shiftKey && !event.metaKey) {
      var searchField = document.getElementById("search");
      searchField.focus();
      searchField.select();
      event.preventDefault();
      return true;
    }
    return false;
  }


  var oldKeyDown = WebInspector.documentKeyDown;
  /**
   * This override allows to intercept keydown events we want to handle in a
   * custom way. Some nested documents (iframes) delegate keydown handling to
   * WebInspector.documentKeyDown (e.g. SourceFrame).
   * @param {KeyboardEvent} event
   * @override
   */
  WebInspector.documentKeyDown = function(event) {
    var isHandled = handleF3Keydown(event);
    if (!isHandled) {
      // Mute refresh action.
      if (event.keyIdentifier == 'F5') {
        event.preventDefault();
      } else if (event.keyIdentifier == 'U+0052' /* 'R' */ &&
          (event.ctrlKey || event.metaKey)) {
        event.preventDefault();
      } else {
        oldKeyDown.call(this, event);
      }
    }
  };
})();


/**
 * This override is necessary for adding script source asynchronously.
 * @override
 */
WebInspector.ScriptView.prototype.setupSourceFrameIfNeeded = function() {
  if (!this._frameNeedsSetup) {
    return;
  }

  this.attach();

  if (this.script.source) {
    this.didResolveScriptSource_();
  } else {
    var self = this;
    devtools.tools.getDebuggerAgent().resolveScriptSource(
        this.script.sourceID,
        function(source) {
          self.script.source = source ||
              WebInspector.UIString('<source is not available>');
          self.didResolveScriptSource_();
        });
  }
};


/**
 * Performs source frame setup when script source is aready resolved.
 */
WebInspector.ScriptView.prototype.didResolveScriptSource_ = function() {
  if (!InspectorController.addSourceToFrame(
      "text/javascript", this.script.source, this.sourceFrame.element)) {
    return;
  }

  delete this._frameNeedsSetup;

  this.sourceFrame.addEventListener(
      "syntax highlighting complete", this._syntaxHighlightingComplete, this);
  this.sourceFrame.syntaxHighlightJavascript();
};


/**
 * @param {string} type Type of the the property value('object' or 'function').
 * @param {string} className Class name of the property value.
 * @constructor
 */
WebInspector.UnresolvedPropertyValue = function(type, className) {
  this.type = type;
  this.className = className;
};


/**
 * This function overrides standard searchableViews getters to perform search
 * only in the current view (other views are loaded asynchronously, no way to
 * search them yet).
 */
WebInspector.searchableViews_ = function() {
  var views = [];
  const visibleView = this.visibleView;
  if (visibleView && visibleView.performSearch) {
    views.push(visibleView);
  }
  return views;
};


/**
 * @override
 */
WebInspector.ResourcesPanel.prototype.__defineGetter__(
    'searchableViews',
    WebInspector.searchableViews_);


/**
 * @override
 */
WebInspector.ScriptsPanel.prototype.__defineGetter__(
    'searchableViews',
    WebInspector.searchableViews_);


(function() {
  var oldShow = WebInspector.ScriptsPanel.prototype.show;
  WebInspector.ScriptsPanel.prototype.show =  function() {
    devtools.tools.getDebuggerAgent().initUI();
    this.enableToggleButton.visible = false;
    oldShow.call(this);
  };
})();


(function InterceptProfilesPanelEvents() {
  var oldShow = WebInspector.ProfilesPanel.prototype.show;
  WebInspector.ProfilesPanel.prototype.show = function() {
    devtools.tools.getDebuggerAgent().initializeProfiling();
    this.enableToggleButton.visible = false;
    oldShow.call(this);
    // Show is called on every show event of a panel, so
    // we only need to intercept it once.
    WebInspector.ProfilesPanel.prototype.show = oldShow;
  };
})();


/*
 * @override
 * TODO(mnaganov): Restore l10n when it will be agreed that it is needed.
 */
WebInspector.UIString = function(string) {
  return String.vsprintf(string, Array.prototype.slice.call(arguments, 1));
};


// There is no clear way of setting frame title yet. So sniffing main resource
// load.
(function OverrideUpdateResource() {
  var originalUpdateResource = WebInspector.updateResource;
  WebInspector.updateResource = function(identifier, payload) {
    originalUpdateResource.call(this, identifier, payload);
    var resource = this.resources[identifier];
    if (resource && resource.mainResource && resource.finished) {
      document.title =
          WebInspector.UIString('Developer Tools - %s', resource.url);
    }
  };
})();


// Highlight extension content scripts in the scripts list.
(function () {
  var original = WebInspector.ScriptsPanel.prototype._addScriptToFilesMenu;
  WebInspector.ScriptsPanel.prototype._addScriptToFilesMenu = function(script) {
    var result = original.apply(this, arguments);
    var debuggerAgent = devtools.tools.getDebuggerAgent();
    var type = debuggerAgent.getScriptContextType(script.sourceID);
    var option = script.filesSelectOption;
    if (type == 'injected' && option) {
      option.addStyleClass('injected');
    }
    return result;
  };
})();


/** Pending WebKit upstream by apavlov). Fixes iframe vs drag problem. */
(function() {
  var originalDragStart = WebInspector.elementDragStart;
  WebInspector.elementDragStart = function(element) {
    if (element) {
      var glassPane = document.createElement("div");
      glassPane.style.cssText =
          'position:absolute;width:100%;height:100%;opacity:0;z-index:1';
      glassPane.id = 'glass-pane-for-drag';
      element.parentElement.appendChild(glassPane);
    }

    originalDragStart.apply(this, arguments);
  };

  var originalDragEnd = WebInspector.elementDragEnd;
  WebInspector.elementDragEnd = function() {
    originalDragEnd.apply(this, arguments);

    var glassPane = document.getElementById('glass-pane-for-drag');
    if (glassPane) {
      glassPane.parentElement.removeChild(glassPane);
    }
  };
})();


(function () {
var orig = InjectedScriptAccess.getProperties;
InjectedScriptAccess.getProperties = function(
    objectProxy, ignoreHasOwnProperty, callback) {
  if (objectProxy.isScope) {
    devtools.tools.getDebuggerAgent().resolveScope(objectProxy.objectId,
        callback);
  } else if (objectProxy.isV8Ref) {
    devtools.tools.getDebuggerAgent().resolveChildren(objectProxy.objectId,
        callback, false);
  } else {
    orig.apply(this, arguments);
  }
};
})();


InjectedScriptAccess.evaluateInCallFrame = function(callFrameId, code,
                                                    objectGroup, callback)
{
  //TODO(pfeldman): remove once 49084 is rolled.
  if (!callback) {
    callback = objectGroup;
  }
  devtools.tools.getDebuggerAgent().evaluateInCallFrame(
      callFrameId, code, callback);
};


WebInspector.resourceTrackingWasEnabled = function()
{
    InspectorController._resourceTrackingEnabled = true;
    this.panels.resources.resourceTrackingWasEnabled();
};

WebInspector.resourceTrackingWasDisabled = function()
{
    InspectorController._resourceTrackingEnabled = false;
    this.panels.resources.resourceTrackingWasDisabled();
};

(function() {
var orig = WebInspector.ConsoleMessage.prototype.setMessageBody;
WebInspector.ConsoleMessage.prototype.setMessageBody = function(args) {
  for (var i = 0; i < args.length; ++i) {
    if (typeof args[i] == "string") {
      args[i] = WebInspector.ObjectProxy.wrapPrimitiveValue(args[i]);
    }
  }
  orig.call(this, args);
};
})();

// Temporary fix for http://crbug/23260.
(function() {
var orig = WebInspector.ResourcesPanel.prototype._createResourceView;
WebInspector.ResourcesPanel.prototype._createResourceView = function(
    resource) {
  if (resource.type == undefined && resource.url) {
    if (resource.url.search('\.js$') != -1) {
      resource.type = WebInspector.Resource.Type.Script;
    } else if (resource.url.search('\.html$') != -1) {
      resource.type = WebInspector.Resource.Type.Document;
    }
  }

  return orig.apply(this, arguments);
};
})();


(function() {
var orig = InjectedScriptAccess.getCompletions;
InjectedScriptAccess.getCompletions = function(expressionString,
    includeInspectorCommandLineAPI, callFrameId, reportCompletions) {
  if (goog.isDef(callFrameId)) {
    devtools.tools.getDebuggerAgent().resolveCompletionsOnFrame(
        expressionString, callFrameId, reportCompletions);
  } else {
    return orig.apply(this, arguments);
  }
};
})();


(function() {
WebInspector.ElementsPanel.prototype._nodeSearchButtonClicked = function(
    event) {
  InspectorController.toggleNodeSearch();
  this.nodeSearchButton.toggled = !this.nodeSearchButton.toggled;
};
})();

/* devtools_host_stub.js */

// Copyright (c) 2009 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/**
 * @fileoverview These stubs emulate backend functionality and allows
 * DevTools frontend to function as a standalone web app.
 */

/**
 * @constructor
 */
RemoteDebuggerAgentStub = function() {
  this.activeProfilerModules_ =
      devtools.DebuggerAgent.ProfilerModules.PROFILER_MODULE_NONE;
  this.profileLogPos_ = 0;
  this.heapProfSample_ = 0;
  this.heapProfLog_ = '';
};


RemoteDebuggerAgentStub.prototype.DebugBreak = function() {
};


RemoteDebuggerAgentStub.prototype.GetContextId = function() {
  RemoteDebuggerAgent.SetContextId(3);
};


RemoteDebuggerAgentStub.prototype.StopProfiling = function(modules) {
  this.activeProfilerModules_ &= ~modules;
};


RemoteDebuggerAgentStub.prototype.StartProfiling = function(modules) {
  var profModules = devtools.DebuggerAgent.ProfilerModules;
  if (modules & profModules.PROFILER_MODULE_HEAP_SNAPSHOT) {
    if (modules & profModules.PROFILER_MODULE_HEAP_STATS) {
      this.heapProfLog_ +=
          'heap-sample-begin,"Heap","allocated",' +
              (new Date()).getTime() + '\n' +
          'heap-sample-stats,"Heap","allocated",10000,1000\n';
      this.heapProfLog_ +=
          'heap-sample-item,STRING_TYPE,100,1000\n' +
          'heap-sample-item,CODE_TYPE,10,200\n' +
          'heap-sample-item,MAP_TYPE,20,350\n';
      this.heapProfLog_ += RemoteDebuggerAgentStub.HeapSamples[this.heapProfSample_++];
      this.heapProfSample_ %= RemoteDebuggerAgentStub.HeapSamples.length;
      this.heapProfLog_ +=
          'heap-sample-end,"Heap","allocated"\n';
    }
  } else {
    this.activeProfilerModules_ |= modules;
  }
};


RemoteDebuggerAgentStub.prototype.GetActiveProfilerModules = function() {
  var self = this;
  setTimeout(function() {
      RemoteDebuggerAgent.DidGetActiveProfilerModules(
          self.activeProfilerModules_);
  }, 100);
};


RemoteDebuggerAgentStub.prototype.GetNextLogLines = function() {
  var profModules = devtools.DebuggerAgent.ProfilerModules;
  var logLines = '';
  if (this.activeProfilerModules_ & profModules.PROFILER_MODULE_CPU) {
    if (this.profileLogPos_ < RemoteDebuggerAgentStub.ProfilerLogBuffer.length) {
      this.profileLogPos_ += RemoteDebuggerAgentStub.ProfilerLogBuffer.length;
      logLines += RemoteDebuggerAgentStub.ProfilerLogBuffer;
    }
  }
  if (this.heapProfLog_) {
    logLines += this.heapProfLog_;
    this.heapProfLog_ = '';
  }
  setTimeout(function() {
    RemoteDebuggerAgent.DidGetNextLogLines(logLines);
  }, 100);
};


/**
 * @constructor
 */
RemoteToolsAgentStub = function() {
};


RemoteToolsAgentStub.prototype.DispatchOnInjectedScript = function() {
};


RemoteToolsAgentStub.prototype.DispatchOnInspectorController = function() {
};


RemoteToolsAgentStub.prototype.ExecuteVoidJavaScript = function() {
};


RemoteDebuggerAgentStub.ProfilerLogBuffer =
  'profiler,begin,1\n' +
  'profiler,resume\n' +
  'code-creation,LazyCompile,0x1000,256,"test1 http://aaa.js:1"\n' +
  'code-creation,LazyCompile,0x2000,256,"test2 http://bbb.js:2"\n' +
  'code-creation,LazyCompile,0x3000,256,"test3 http://ccc.js:3"\n' +
  'tick,0x1010,0x0,3\n' +
  'tick,0x2020,0x0,3,0x1010\n' +
  'tick,0x2020,0x0,3,0x1010\n' +
  'tick,0x3010,0x0,3,0x2020, 0x1010\n' +
  'tick,0x2020,0x0,3,0x1010\n' +
  'tick,0x2030,0x0,3,0x2020, 0x1010\n' +
  'tick,0x2020,0x0,3,0x1010\n' +
  'tick,0x1010,0x0,3\n' +
  'profiler,pause\n';


RemoteDebuggerAgentStub.HeapSamples = [
  'heap-js-cons-item,foo,1,100\n' +
  'heap-js-cons-item,bar,20,2000\n' +
  'heap-js-cons-item,Object,5,100\n' +
  'heap-js-ret-item,foo,bar;3\n' +
  'heap-js-ret-item,bar,foo;5\n' +
  'heap-js-ret-item,Object:0x1234,(roots);1\n',

  'heap-js-cons-item,foo,2000,200000\n' +
  'heap-js-cons-item,bar,10,1000\n' +
  'heap-js-cons-item,Object,6,120\n' +
  'heap-js-ret-item,foo,bar;7,Object:0x1234;10\n' +
  'heap-js-ret-item,bar,foo;10,Object:0x1234;10\n' +
  'heap-js-ret-item,Object:0x1234,(roots);1\n',

  'heap-js-cons-item,foo,15,1500\n' +
  'heap-js-cons-item,bar,15,1500\n' +
  'heap-js-cons-item,Object,5,100\n' +
  'heap-js-cons-item,Array,3,1000\n' +
  'heap-js-ret-item,foo,bar;3,Array:0x5678;1\n' +
  'heap-js-ret-item,bar,foo;5,Object:0x1234;8,Object:0x5678;2\n' +
  'heap-js-ret-item,Object:0x1234,(roots);1,Object:0x5678;2\n' +
  'heap-js-ret-item,Object:0x5678,(global property);3,Object:0x1234;5\n' +
  'heap-js-ret-item,Array:0x5678,(global property);3,Array:0x5678;2\n',

  'heap-js-cons-item,bar,20,2000\n' +
  'heap-js-cons-item,Object,6,120\n' +
  'heap-js-ret-item,bar,foo;5,Object:0x1234;1,Object:0x1235;3\n' +
  'heap-js-ret-item,Object:0x1234,(global property);3\n' +
  'heap-js-ret-item,Object:0x1235,(global property);5\n',

  'heap-js-cons-item,foo,15,1500\n' +
  'heap-js-cons-item,bar,15,1500\n' +
  'heap-js-cons-item,Array,10,1000\n' +
  'heap-js-ret-item,foo,bar;1,Array:0x5678;1\n' +
  'heap-js-ret-item,bar,foo;5\n' +
  'heap-js-ret-item,Array:0x5678,(roots);3\n',

  'heap-js-cons-item,bar,20,2000\n' +
  'heap-js-cons-item,baz,15,1500\n' +
  'heap-js-ret-item,bar,baz;3\n' +
  'heap-js-ret-item,baz,bar;3\n'
];


/**
 * @constructor
 */
RemoteDebuggerCommandExecutorStub = function() {
};


RemoteDebuggerCommandExecutorStub.prototype.DebuggerCommand = function(cmd) {
  if ('{"seq":2,"type":"request","command":"scripts","arguments":{"' +
      'includeSource":false}}' == cmd) {
    var response1 =
        '{"seq":5,"request_seq":2,"type":"response","command":"scripts","' +
        'success":true,"body":[{"handle":61,"type":"script","name":"' +
        'http://www/~test/t.js","id":59,"lineOffset":0,"columnOffset":0,' +
        '"lineCount":1,"sourceStart":"function fib(n) {","sourceLength":300,' +
        '"scriptType":2,"compilationType":0,"context":{"ref":60}}],"refs":[{' +
        '"handle":60,"type":"context","data":"page,3"}],"running":false}';
    this.sendResponse_(response1);
  } else if ('{"seq":3,"type":"request","command":"scripts","arguments":{' +
             '"ids":[59],"includeSource":true}}' == cmd) {
    this.sendResponse_(
        '{"seq":8,"request_seq":3,"type":"response","command":"scripts",' +
        '"success":true,"body":[{"handle":1,"type":"script","name":' +
        '"http://www/~test/t.js","id":59,"lineOffset":0,"columnOffset":0,' +
        '"lineCount":1,"source":"function fib(n) {return n+1;}",' +
        '"sourceLength":244,"scriptType":2,"compilationType":0,"context":{' +
        '"ref":0}}],"refs":[{"handle":0,"type":"context","data":"page,3}],"' +
        '"running":false}');
  } else {
    debugPrint('Unexpected command: ' + cmd);
  }
};


RemoteDebuggerCommandExecutorStub.prototype.sendResponse_ = function(response) {
  setTimeout(function() {
    RemoteDebuggerAgent.DebuggerOutput(response);
  }, 0);
};


/**
 * @constructor
 */
DevToolsHostStub = function() {
  this.isStub = true;
  window.domAutomationController = {
    send: function(text) {
        debugPrint(text);
    }
  };
};


function addDummyResource() {
  var payload = {
    requestHeaders : {},
    requestURL: 'http://google.com/simple_page.html',
    host: 'google.com',
    path: 'simple_page.html',
    lastPathComponent: 'simple_page.html',
    isMainResource: true,
    cached: false,
    mimeType: 'text/html',
    suggestedFilename: 'simple_page.html',
    expectedContentLength: 10000,
    statusCode: 200,
    contentLength: 10000,
    responseHeaders: {},
    type: WebInspector.Resource.Type.Document,
    finished: true,
    startTime: new Date(),

    didResponseChange: true,
    didCompletionChange: true,
    didTypeChange: true
  };

  WebInspector.addResource(1, payload);
  WebInspector.updateResource(1, payload);
}


DevToolsHostStub.prototype.loaded = function() {
  addDummyResource();
};


DevToolsHostStub.prototype.reset = function() {
};


DevToolsHostStub.prototype.getPlatform = function() {
  return "windows";
};


DevToolsHostStub.prototype.hiddenPanels = function() {
  return "";
};


DevToolsHostStub.prototype.addResourceSourceToFrame = function(
    identifier, mimeType, element) {
};


DevToolsHostStub.prototype.addSourceToFrame = function(mimeType, source,
    element) {
};


DevToolsHostStub.prototype.getApplicationLocale = function() {
  return "en-US";
};


if (!window['DevToolsHost']) {
  window['RemoteDebuggerAgent'] = new RemoteDebuggerAgentStub();
  window['RemoteDebuggerCommandExecutor'] =
      new RemoteDebuggerCommandExecutorStub();
  window['RemoteToolsAgent'] = new RemoteToolsAgentStub();
  window['DevToolsHost'] = new DevToolsHostStub();
}

/* tests.js */

// Copyright (c) 2009 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.


/**
 * @fileoverview This file contains small testing framework along with the
 * test suite for the frontend. These tests are a part of the continues build
 * and are executed by the devtools_sanity_unittest.cc as a part of the
 * Interactive UI Test suite.
 */

if (window.domAutomationController) {

var ___interactiveUiTestsMode = true;

/**
 * Test suite for interactive UI tests.
 * @constructor
 */
TestSuite = function() {
  this.controlTaken_ = false;
  this.timerId_ = -1;
};


/**
 * Reports test failure.
 * @param {string} message Failure description.
 */
TestSuite.prototype.fail = function(message) {
  if (this.controlTaken_) {
    this.reportFailure_(message);
  } else {
    throw message;
  }
};


/**
 * Equals assertion tests that expected == actual.
 * @param {Object} expected Expected object.
 * @param {Object} actual Actual object.
 * @param {string} opt_message User message to print if the test fails.
 */
TestSuite.prototype.assertEquals = function(expected, actual, opt_message) {
  if (expected != actual) {
    var message = 'Expected: "' + expected + '", but was "' + actual + '"';
    if (opt_message) {
      message = opt_message + '(' + message + ')';
    }
    this.fail(message);
  }
};


/**
 * True assertion tests that value == true.
 * @param {Object} value Actual object.
 * @param {string} opt_message User message to print if the test fails.
 */
TestSuite.prototype.assertTrue = function(value, opt_message) {
  this.assertEquals(true, !!value, opt_message);
};


/**
 * Contains assertion tests that string contains substring.
 * @param {string} string Outer.
 * @param {string} substring Inner.
 */
TestSuite.prototype.assertContains = function(string, substring) {
  if (string.indexOf(substring) == -1) {
    this.fail('Expected to: "' + string + '" to contain "' + substring + '"');
  }
};


/**
 * Takes control over execution.
 */
TestSuite.prototype.takeControl = function() {
  this.controlTaken_ = true;
  // Set up guard timer.
  var self = this;
  this.timerId_ = setTimeout(function() {
    self.reportFailure_('Timeout exceeded: 20 sec');
  }, 20000);
};


/**
 * Releases control over execution.
 */
TestSuite.prototype.releaseControl = function() {
  if (this.timerId_ != -1) {
    clearTimeout(this.timerId_);
    this.timerId_ = -1;
  }
  this.reportOk_();
};


/**
 * Async tests use this one to report that they are completed.
 */
TestSuite.prototype.reportOk_ = function() {
  window.domAutomationController.send('[OK]');
};


/**
 * Async tests use this one to report failures.
 */
TestSuite.prototype.reportFailure_ = function(error) {
  if (this.timerId_ != -1) {
    clearTimeout(this.timerId_);
    this.timerId_ = -1;
  }
  window.domAutomationController.send('[FAILED] ' + error);
};


/**
 * Runs all global functions starting with 'test' as unit tests.
 */
TestSuite.prototype.runTest = function(testName) {
  try {
    this[testName]();
    if (!this.controlTaken_) {
      this.reportOk_();
    }
  } catch (e) {
    this.reportFailure_(e);
  }
};


/**
 * @param {string} panelName Name of the panel to show.
 */
TestSuite.prototype.showPanel = function(panelName) {
  // Open Scripts panel.
  var toolbar = document.getElementById('toolbar');
  var button = toolbar.getElementsByClassName(panelName)[0];
  button.click();
  this.assertEquals(WebInspector.panels[panelName],
      WebInspector.currentPanel);
};


/**
 * Overrides the method with specified name until it's called first time.
 * @param {Object} receiver An object whose method to override.
 * @param {string} methodName Name of the method to override.
 * @param {Function} override A function that should be called right after the
 *     overriden method returns.
 * @param {boolean} opt_sticky Whether restore original method after first run
 *     or not.
 */
TestSuite.prototype.addSniffer = function(receiver, methodName, override,
                                          opt_sticky) {
  var orig = receiver[methodName];
  if (typeof orig != 'function') {
    this.fail('Cannot find method to override: ' + methodName);
  }
  var test = this;
  receiver[methodName] = function(var_args) {
    try {
      var result = orig.apply(this, arguments);
    } finally {
      if (!opt_sticky) {
        receiver[methodName] = orig;
      }
    }
    // In case of exception the override won't be called.
    try {
      override.apply(this, arguments);
    } catch (e) {
      test.fail('Exception in overriden method "' + methodName + '": ' + e);
    }
    return result;
  };
};


// UI Tests


/**
 * Tests that the real injected host is present in the context.
 */
TestSuite.prototype.testHostIsPresent = function() {
  this.assertTrue(typeof DevToolsHost == 'object' && !DevToolsHost.isStub);
};


/**
 * Tests elements tree has an 'HTML' root.
 */
TestSuite.prototype.testElementsTreeRoot = function() {
  var doc = WebInspector.domAgent.document;
  this.assertEquals('HTML', doc.documentElement.nodeName);
  this.assertTrue(doc.documentElement.hasChildNodes());
};


/**
 * Tests that main resource is present in the system and that it is
 * the only resource.
 */
TestSuite.prototype.testMainResource = function() {
  var tokens = [];
  var resources = WebInspector.resources;
  for (var id in resources) {
    tokens.push(resources[id].lastPathComponent);
  }
  this.assertEquals('simple_page.html', tokens.join(','));
};


/**
 * Tests that resources tab is enabled when corresponding item is selected.
 */
TestSuite.prototype.testEnableResourcesTab = function() {
  this.showPanel('resources');

  var test = this;
  this.addSniffer(WebInspector, 'addResource',
      function(identifier, payload) {
        test.assertEquals('simple_page.html', payload.lastPathComponent);
        WebInspector.panels.resources.refresh();
        WebInspector.panels.resources.revealAndSelectItem(
            WebInspector.resources[identifier]);

        test.releaseControl();
      });

  // Following call should lead to reload that we capture in the
  // addResource override.
  WebInspector.panels.resources._enableResourceTracking();

  // We now have some time to report results to controller.
  this.takeControl();
};


/**
 * Tests that correct content length is reported for resources.
 */
TestSuite.prototype.testResourceContentLength = function() {
  this.showPanel('resources');
  var test = this;

  var png = false;
  var html = false;
  this.addSniffer(WebInspector, 'updateResource',
      function(identifier, payload) {
        if (!payload.didLengthChange)
          return;
        var resource = WebInspector.resources[identifier];
        if (!resource || !resource.url)
          return;
        if (resource.url.search('image.html$') != -1) {
          var expectedLength = 87;
          test.assertTrue(
              resource.contentLength <= expectedLength,
              'image.html content length is greater thatn expected.');
          if (expectedLength == resource.contentLength) {
            html = true;
          }
        } else if (resource.url.search('image.png') != -1) {
          var expectedLength = 257796;
          test.assertTrue(
              resource.contentLength <= expectedLength,
              'image.png content length is greater than expected.');
          if (expectedLength == resource.contentLength) {
            png = true;
          }
        }
        if (html && png) {
          // Wait 1 second before releasing control to check that the content
          // lengths are not updated anymore.
          setTimeout(function() {
            test.releaseControl();
          }, 1000);
        }
      }, true);

  // Make sure resource tracking is on.
  WebInspector.panels.resources._enableResourceTracking();
  // Reload inspected page to update all resources.
  test.evaluateInConsole_(
      'window.location.reload(true);',
       function(resultText) {
           test.assertEquals('undefined', resultText,
                             'Unexpected result of reload().');
       });

  // We now have some time to report results to controller.
  this.takeControl();
};


/**
 * Tests resource headers.
 */
TestSuite.prototype.testResourceHeaders = function() {
  this.showPanel('resources');

  var test = this;

  var requestOk = false;
  var responseOk = false;
  var timingOk = false;

  this.addSniffer(WebInspector, 'addResource',
      function(identifier, payload) {
        var resource = this.resources[identifier];
        if (resource.mainResource) {
          // We are only interested in secondary resources in this test.
          return;
        }

        var requestHeaders = JSON.stringify(resource.requestHeaders);
        test.assertContains(requestHeaders, 'Accept');
        requestOk = true;
      }, true);

  this.addSniffer(WebInspector, 'updateResource',
      function(identifier, payload) {
        var resource = this.resources[identifier];
        if (resource.mainResource) {
          // We are only interested in secondary resources in this test.
          return;
        }

        if (payload.didResponseChange) {
          var responseHeaders = JSON.stringify(resource.responseHeaders);
          test.assertContains(responseHeaders, 'Content-type');
          test.assertContains(responseHeaders, 'Content-Length');
          test.assertTrue(typeof resource.responseReceivedTime != 'undefnied');
          responseOk = true;
        }

        if (payload.didTimingChange) {
          test.assertTrue(typeof resource.startTime != 'undefnied');
          timingOk = true;
        }

        if (payload.didCompletionChange) {
          test.assertTrue(requestOk);
          test.assertTrue(responseOk);
          test.assertTrue(timingOk);
          test.assertTrue(typeof resource.endTime != 'undefnied');
          test.releaseControl();
        }
      }, true);

  WebInspector.panels.resources._enableResourceTracking();
  this.takeControl();
};


/**
 * Test that profiler works.
 */
TestSuite.prototype.testProfilerTab = function() {
  this.showPanel('profiles');

  var test = this;
  this.addSniffer(WebInspector.panels.profiles, 'addProfileHeader',
      function(typeOrProfile, profile) {
        if (!profile) {
          profile = typeOrProfile;
        }
        var panel = WebInspector.panels.profiles;
        panel.showProfile(profile);
        var node = panel.visibleView.profileDataGridTree.children[0];
        // Iterate over displayed functions and search for a function
        // that is called 'fib' or 'eternal_fib'. If found, it will mean
        // that we actually have profiled page's code.
        while (node) {
          if (node.functionName.indexOf('fib') != -1) {
            test.releaseControl();
          }
          node = node.traverseNextNode(true, null, true);
        }

        test.fail();
      });
  var ticksCount = 0;
  var tickRecord = '\nt,';
  this.addSniffer(RemoteDebuggerAgent, 'DidGetNextLogLines',
      function(log) {
        var pos = 0;
        while ((pos = log.indexOf(tickRecord, pos)) != -1) {
          pos += tickRecord.length;
          ticksCount++;
        }
        if (ticksCount > 100) {
          InspectorController.stopProfiling();
        }
      }, true);

  InspectorController.startProfiling();
  this.takeControl();
};


/**
 * Tests that scripts tab can be open and populated with inspected scripts.
 */
TestSuite.prototype.testShowScriptsTab = function() {
  var parsedDebuggerTestPageHtml = false;

  // Intercept parsedScriptSource calls to check that all expected scripts are
  // added to the debugger.
  var test = this;
  var receivedConsoleApiSource = false;
  this.addSniffer(WebInspector, 'parsedScriptSource',
      function(sourceID, sourceURL, source, startingLine) {
        if (sourceURL == undefined) {
          if (receivedConsoleApiSource) {
            test.fail('Unexpected script without URL');
          } else {
            receivedConsoleApiSource = true;
          }
        } else if (sourceURL.search(/debugger_test_page.html$/) != -1) {
          if (parsedDebuggerTestPageHtml) {
            test.fail('Unexpected parse event: ' + sourceURL);
          }
          parsedDebuggerTestPageHtml = true;
        } else {
          test.fail('Unexpected script URL: ' + sourceURL);
        }

        if (!WebInspector.panels.scripts.visibleView) {
          test.fail('No visible script view: ' + sourceURL);
        }

        // There should be two scripts: one for the main page and another
        // one which is source of console API(see
        // InjectedScript._ensureCommandLineAPIInstalled).
        if (parsedDebuggerTestPageHtml && receivedConsoleApiSource) {
           test.releaseControl();
        }
      }, true /* sticky */);

  this.showPanel('scripts');

  // Wait until all scripts are added to the debugger.
  this.takeControl();
};


/**
 * Tests that scripts tab is populated with inspected scripts even if it
 * hadn't been shown by the moment inspected paged refreshed.
 * @see http://crbug.com/26312
 */
TestSuite.prototype.testScriptsTabIsPopulatedOnInspectedPageRefresh =
    function() {
  var test = this;
  this.assertEquals(WebInspector.panels.elements,
      WebInspector.currentPanel, 'Elements panel should be current one.');

  this.addSniffer(devtools.DebuggerAgent.prototype, 'reset',
      waitUntilScriptIsParsed);

  // Reload inspected page. It will reset the debugger agent.
  test.evaluateInConsole_(
      'window.location.reload(true);',
      function(resultText) {
        test.assertEquals('undefined', resultText,
                          'Unexpected result of reload().');
      });

  function waitUntilScriptIsParsed() {
    var parsed = devtools.tools.getDebuggerAgent().parsedScripts_;
    for (var id in parsed) {
      var url = parsed[id].getUrl();
      if (url && url.search('debugger_test_page.html$') != -1) {
        checkScriptsPanel();
        return;
      }
    }
    test.addSniffer(devtools.DebuggerAgent.prototype, 'addScriptInfo_',
        waitUntilScriptIsParsed);
  }

  function checkScriptsPanel() {
    test.showPanel('scripts');
    test.assertTrue(test._scriptsAreParsed(['debugger_test_page.html$']),
                    'Inspected script not found in the scripts list');
    test.releaseControl();
  }

  // Wait until all scripts are added to the debugger.
  this.takeControl();
};


/**
 * Tests that scripts list contains content scripts.
 */
TestSuite.prototype.testContentScriptIsPresent = function() {
  this.showPanel('scripts');
  var test = this;

  test._waitUntilScriptsAreParsed(
      ['page_with_content_script.html$', 'simple_content_script.js$'],
      function() {
        test.releaseControl();
      });

  // Wait until all scripts are added to the debugger.
  this.takeControl();
};


/**
 * Tests that scripts are not duplicaed on Scripts tab switch.
 */
TestSuite.prototype.testNoScriptDuplicatesOnPanelSwitch = function() {
  var test = this;

  // There should be two scripts: one for the main page and another
  // one which is source of console API(see
  // InjectedScript._ensureCommandLineAPIInstalled).
  var expectedScriptsCount = 2;
  var parsedScripts = [];

  this.showPanel('scripts');


  function switchToElementsTab() {
    test.showPanel('elements');
    setTimeout(switchToScriptsTab, 0);
  }

  function switchToScriptsTab() {
    test.showPanel('scripts');
    setTimeout(checkScriptsPanel, 0);
  }

  function checkScriptsPanel() {
    test.assertTrue(!!WebInspector.panels.scripts.visibleView,
                    'No visible script view.');
    test.assertTrue(test._scriptsAreParsed(['debugger_test_page.html$']),
                    'Some scripts are missing.');
    checkNoDuplicates();
    test.releaseControl();
  }

  function checkNoDuplicates() {
    var scriptSelect = document.getElementById('scripts-files');
    var options = scriptSelect.options;
    for (var i = 0; i < options.length; i++) {
      var scriptName = options[i].text;
      for (var j = i + 1; j < options.length; j++) {
        test.assertTrue(scriptName != options[j].text,
            'Found script duplicates: ' + test.optionsToString_(options));
      }
    }
  }

  test._waitUntilScriptsAreParsed(
      ['debugger_test_page.html$'],
      function() {
        checkNoDuplicates();
        setTimeout(switchToElementsTab, 0);
      });


  // Wait until all scripts are added to the debugger.
  this.takeControl();
};


/**
 * Tests that a breakpoint can be set.
 */
TestSuite.prototype.testSetBreakpoint = function() {
  var test = this;
  this.showPanel('scripts');

  var breakpointLine = 12;

  this._waitUntilScriptsAreParsed(['debugger_test_page.html'],
      function() {
        test.showMainPageScriptSource_(
            'debugger_test_page.html',
            function(view, url) {
              view._addBreakpoint(breakpointLine);
              // Force v8 execution.
              RemoteToolsAgent.ExecuteVoidJavaScript();
              test.waitForSetBreakpointResponse_(url, breakpointLine,
                  function() {
                    test.releaseControl();
                  });
            });
      });

  this.takeControl();
};


/**
 * Tests that pause on exception works.
 */
TestSuite.prototype.testPauseOnException = function() {
  this.showPanel('scripts');
  var test = this;

  // Make sure pause on exceptions is on.
  if (!WebInspector.currentPanel.pauseOnExceptionButton.toggled) {
    WebInspector.currentPanel.pauseOnExceptionButton.element.click();
  }

  this._executeCodeWhenScriptsAreParsed(
      'handleClick()',
      ['pause_on_exception.html$']);

  this._waitForScriptPause(
      {
        functionsOnStack: ['throwAnException', 'handleClick',
                           '(anonymous function)'],
        lineNumber: 6,
        lineText: '  return unknown_var;'
      },
      function() {
        test.releaseControl();
      });

  this.takeControl();
};


// Tests that debugger works correctly if pause event occurs when DevTools
// frontend is being loaded.
TestSuite.prototype.testPauseWhenLoadingDevTools = function() {
  this.showPanel('scripts');
  var test = this;

  var expectations = {
        functionsOnStack: ['callDebugger'],
        lineNumber: 8,
        lineText: '  debugger;'
      };


  // Script execution can already be paused.
  if (WebInspector.currentPanel.paused) {
    var callFrame =
        WebInspector.currentPanel.sidebarPanes.callstack.selectedCallFrame;
    this.assertEquals(expectations.functionsOnStack[0],
                      callFrame.functionName);
    var callbackInvoked = false;
    this._checkSourceFrameWhenLoaded(expectations, function() {
          callbackInvoked = true;
          if (test.controlTaken_) {
            test.releaseControl();
          }
        });
    if (!callbackInvoked) {
      test.takeControl();
    }
    return;
  }

  this._waitForScriptPause(
      {
        functionsOnStack: ['callDebugger'],
        lineNumber: 8,
        lineText: '  debugger;'
      },
      function() {
        test.releaseControl();
      });
  this.takeControl();
};


/**
 * Serializes options collection to string.
 * @param {HTMLOptionsCollection} options
 * @return {string}
 */
TestSuite.prototype.optionsToString_ = function(options) {
  var names = [];
  for (var i = 0; i < options.length; i++) {
    names.push('"' + options[i].text + '"');
  }
  return names.join(',');
};


/**
 * Ensures that main HTML resource is selected in Scripts panel and that its
 * source frame is setup. Invokes the callback when the condition is satisfied.
 * @param {HTMLOptionsCollection} options
 * @param {function(WebInspector.SourceView,string)} callback
 */
TestSuite.prototype.showMainPageScriptSource_ = function(scriptName, callback) {
  var test = this;

  var scriptSelect = document.getElementById('scripts-files');
  var options = scriptSelect.options;

  test.assertTrue(options.length, 'Scripts list is empty');

  // Select page's script if it's not current option.
  var scriptResource;
  if (options[scriptSelect.selectedIndex].text === scriptName) {
    scriptResource = options[scriptSelect.selectedIndex].representedObject;
  } else {
    var pageScriptIndex = -1;
    for (var i = 0; i < options.length; i++) {
      if (options[i].text === scriptName) {
        pageScriptIndex = i;
        break;
      }
    }
    test.assertTrue(-1 !== pageScriptIndex,
                    'Script with url ' + scriptName + ' not found among ' +
                        test.optionsToString_(options));
    scriptResource = options[pageScriptIndex].representedObject;

    // Current panel is 'Scripts'.
    WebInspector.currentPanel._showScriptOrResource(scriptResource);
    test.assertEquals(pageScriptIndex, scriptSelect.selectedIndex,
                      'Unexpected selected option index.');
  }

  test.assertTrue(scriptResource instanceof WebInspector.Resource,
                  'Unexpected resource class.');
  test.assertTrue(!!scriptResource.url, 'Resource URL is null.');
  test.assertTrue(
      scriptResource.url.search(scriptName + '$') != -1,
      'Main HTML resource should be selected.');

  var scriptsPanel = WebInspector.panels.scripts;

  var view = scriptsPanel.visibleView;
  test.assertTrue(view instanceof WebInspector.SourceView);

  if (!view.sourceFrame._isContentLoaded()) {
    test.addSniffer(view, '_sourceFrameSetupFinished', function(event) {
      callback(view, scriptResource.url);
    });
  } else {
    callback(view, scriptResource.url);
  }
};


/*
 * Evaluates the code in the console as if user typed it manually and invokes
 * the callback when the result message is received and added to the console.
 * @param {string} code
 * @param {function(string)} callback
 */
TestSuite.prototype.evaluateInConsole_ = function(code, callback) {
  WebInspector.console.visible = true;
  WebInspector.console.prompt.text = code;
  WebInspector.console.promptElement.handleKeyEvent(
      new TestSuite.KeyEvent('Enter'));

  this.addSniffer(WebInspector.ConsoleView.prototype, 'addMessage',
      function(commandResult) {
        callback(commandResult.toMessageElement().textContent);
      });
};


/*
 * Waits for 'setbreakpoint' response, checks that corresponding breakpoint
 * was successfully set and invokes the callback if it was.
 * @param {string} scriptUrl
 * @param {number} breakpointLine
 * @param {function()} callback
 */
TestSuite.prototype.waitForSetBreakpointResponse_ = function(scriptUrl,
                                                             breakpointLine,
                                                             callback) {
  var test = this;
  test.addSniffer(
      devtools.DebuggerAgent.prototype,
      'handleSetBreakpointResponse_',
      function(msg) {
        var bps = this.urlToBreakpoints_[scriptUrl];
        test.assertTrue(!!bps, 'No breakpoints for line ' + breakpointLine);
        var line = devtools.DebuggerAgent.webkitToV8LineNumber_(breakpointLine);
        test.assertTrue(!!bps[line].getV8Id(),
                        'Breakpoint id was not assigned.');
        callback();
      });
};


/**
 * Tests eval on call frame.
 */
TestSuite.prototype.testEvalOnCallFrame = function() {
  this.showPanel('scripts');

  var breakpointLine = 16;

  var test = this;
  this.addSniffer(devtools.DebuggerAgent.prototype, 'handleScriptsResponse_',
      function(msg) {
        test.showMainPageScriptSource_(
            'debugger_test_page.html',
            function(view, url) {
              view._addBreakpoint(breakpointLine);
              // Force v8 execution.
              RemoteToolsAgent.ExecuteVoidJavaScript();
              test.waitForSetBreakpointResponse_(url, breakpointLine,
                                                 setBreakpointCallback);
            });
      });

  function setBreakpointCallback() {
    // Since breakpoints are ignored in evals' calculate() function is
    // execute after zero-timeout so that the breakpoint is hit.
    test.evaluateInConsole_(
        'setTimeout("calculate(123)" , 0)',
        function(resultText) {
          test.assertTrue(!isNaN(resultText),
                          'Failed to get timer id: ' + resultText);
          waitForBreakpointHit();
        });
  }

  function waitForBreakpointHit() {
    test.addSniffer(
        devtools.DebuggerAgent.prototype,
        'handleBacktraceResponse_',
        function(msg) {
          test.assertEquals(2, this.callFrames_.length,
                            'Unexpected stack depth on the breakpoint. ' +
                                JSON.stringify(msg));
          test.assertEquals('calculate', this.callFrames_[0].functionName,
                            'Unexpected top frame function.');
          // Evaluate 'e+1' where 'e' is an argument of 'calculate' function.
          test.evaluateInConsole_(
              'e+1',
              function(resultText) {
                test.assertEquals('124', resultText, 'Unexpected "e+1" value.');
                test.releaseControl();
              });
        });
  }

  this.takeControl();
};


/**
 * Tests that console auto completion works when script execution is paused.
 */
TestSuite.prototype.testCompletionOnPause = function() {
  this.showPanel('scripts');
  var test = this;
  this._executeCodeWhenScriptsAreParsed(
      'handleClick()',
      ['completion_on_pause.html$']);

  this._waitForScriptPause(
      {
        functionsOnStack: ['innerFunction', 'handleClick',
                           '(anonymous function)'],
        lineNumber: 9,
        lineText: '    debugger;'
      },
      showConsole);

  function showConsole() {
    test.addSniffer(WebInspector.console, 'afterShow', testLocalsCompletion);
    WebInspector.showConsole();
  }

  function testLocalsCompletion() {
    checkCompletions(
        'th',
        ['parameter1', 'closureLocal', 'p', 'createClosureLocal'],
        testThisCompletion);
  }

  function testThisCompletion() {
    checkCompletions(
        'this.',
        ['field1', 'field2', 'm'],
        testFieldCompletion);
  }

  function testFieldCompletion() {
    checkCompletions(
        'this.field1.',
        ['id', 'name'],
        function() {
          test.releaseControl();
        });
  }

  function checkCompletions(expression, expectedProperties, callback) {
      test.addSniffer(WebInspector.console, '_reportCompletions',
        function(bestMatchOnly, completionsReadyCallback, dotNotation,
            bracketNotation, prefix, result, isException) {
          test.assertTrue(!isException,
                          'Exception while collecting completions');
          for (var i = 0; i < expectedProperties.length; i++) {
            var name = expectedProperties[i];
            test.assertTrue(result[name], 'Name ' + name +
                ' not found among the completions: ' +
                JSON.stringify(result));
          }
          setTimeout(callback, 0);
        });
    WebInspector.console.prompt.text = expression;
    WebInspector.console.prompt.autoCompleteSoon();
  }

  this.takeControl();
};


/**
 * Tests that inspected page doesn't hang on reload if it contains a syntax
 * error and DevTools window is open.
 */
TestSuite.prototype.testAutoContinueOnSyntaxError = function() {
  this.showPanel('scripts');
  var test = this;

  function checkScriptsList() {
    var scriptSelect = document.getElementById('scripts-files');
    var options = scriptSelect.options;
    // There should be only console API source (see
    // InjectedScript._ensureCommandLineAPIInstalled) since the page script
    // contains a syntax error.
    for (var i = 0 ; i < options.length; i++) {
      if (options[i].text.search('script_syntax_error.html$') != -1) {
        test.fail('Script with syntax error should not be in the list of ' +
                  'parsed scripts.');
      }
    }
  }

  this.addSniffer(devtools.DebuggerAgent.prototype, 'handleScriptsResponse_',
      function(msg) {
        checkScriptsList();

        // Reload inspected page.
        test.evaluateInConsole_(
            'window.location.reload(true);',
            function(resultText) {
              test.assertEquals('undefined', resultText,
                                'Unexpected result of reload().');
              waitForExceptionEvent();
            });
      });

  function waitForExceptionEvent() {
    var exceptionCount = 0;
    test.addSniffer(
        devtools.DebuggerAgent.prototype,
        'handleExceptionEvent_',
        function(msg) {
          exceptionCount++;
          test.assertEquals(1, exceptionCount, 'Too many exceptions.');
          test.assertEquals(undefined, msg.getBody().script,
                            'Unexpected exception: ' + JSON.stringify(msg));
          test.releaseControl();
        });

    // Check that the script is not paused on parse error.
    test.addSniffer(
        WebInspector,
        'pausedScript',
        function(callFrames) {
          test.fail('Script execution should not pause on syntax error.');
        });
  }

  this.takeControl();
};


/**
 * Checks current execution line against expectations.
 * @param {WebInspector.SourceFrame} sourceFrame
 * @param {number} lineNumber Expected line number
 * @param {string} lineContent Expected line text
 */
TestSuite.prototype._checkExecutionLine = function(sourceFrame, lineNumber,
                                                   lineContent) {
  var sourceRow = sourceFrame.sourceRow(lineNumber);

  var line = sourceRow.getElementsByClassName('webkit-line-content')[0];
  this.assertEquals(lineNumber, sourceFrame.executionLine,
                    'Unexpected execution line number.');
  this.assertEquals(lineContent, line.textContent,
                    'Unexpected execution line text.');

  this.assertTrue(!!sourceRow, 'Execution line not found');
  this.assertTrue(sourceRow.hasStyleClass('webkit-execution-line'),
      'Execution line ' + lineNumber + ' is not highlighted. Class: ' +
          sourceRow.className);
}


/**
 * Checks that all expected scripts are present in the scripts list
 * in the Scripts panel.
 * @param {Array.<string>} expected Regular expressions describing
 *     expected script names.
 * @return {boolean} Whether all the scripts are in 'scripts-files' select
 *     box
 */
TestSuite.prototype._scriptsAreParsed = function(expected) {
  var scriptSelect = document.getElementById('scripts-files');
  var options = scriptSelect.options;

  // Check that at least all the expected scripts are present.
  var missing = expected.slice(0);
  for (var i = 0 ; i < options.length; i++) {
    for (var j = 0; j < missing.length; j++) {
      if (options[i].text.search(missing[j]) != -1) {
        missing.splice(j, 1);
        break;
      }
    }
  }
  return missing.length == 0;
};


/**
 * Waits for script pause, checks expectations, and invokes the callback.
 * @param {Object} expectations  Dictionary of expectations
 * @param {function():void} callback
 */
TestSuite.prototype._waitForScriptPause = function(expectations, callback) {
  var test = this;
  // Wait until script is paused.
  test.addSniffer(
      WebInspector,
      'pausedScript',
      function(callFrames) {
        test.assertEquals(expectations.functionsOnStack.length,
                          callFrames.length,
                          'Unexpected stack depth');

        var functionsOnStack = [];
        for (var i = 0; i < callFrames.length; i++) {
          functionsOnStack.push(callFrames[i].functionName);
        }
        test.assertEquals(
            expectations.functionsOnStack.join(','),
            functionsOnStack.join(','), 'Unexpected stack.');

        // Check that execution line where the script is paused is
        // expected one.
        test._checkSourceFrameWhenLoaded(expectations, callback);
      });
};


/**
 * Waits for current source frame to load, checks expectations, and invokes
 * the callback.
 * @param {Object} expectations  Dictionary of expectations
 * @param {function():void} callback
 */
TestSuite.prototype._checkSourceFrameWhenLoaded = function(
    expectations, callback) {
  var test = this;

  var frame = WebInspector.currentPanel.visibleView.sourceFrame;
  if (frame._isContentLoaded()) {
    checkExecLine();
  } else {
    frame.addEventListener('content loaded', checkExecLine);
  }
  function checkExecLine() {
    test._checkExecutionLine(frame, expectations.lineNumber,
                             expectations.lineText);
    // Make sure we don't listen anymore.
    frame.removeEventListener('content loaded', checkExecLine);
    callback();
  }
};


/**
 * Performs sequence of steps.
 * @param {Array.<Object|Function>} Array [expectations1,action1,expectations2,
 *     action2,...,actionN].
 */
TestSuite.prototype._performSteps = function(actions) {
  var test = this;
  var i = 0;
  function doNextAction() {
    if (i > 0) {
      actions[i++]();
    }
    if (i < actions.length - 1) {
      test._waitForScriptPause(actions[i++], doNextAction);
    }
  }
  doNextAction();
};


/**
 * Waits until all the scripts are parsed and asynchronously executes the code
 * in the inspected page.
 */
TestSuite.prototype._executeCodeWhenScriptsAreParsed = function(
    code, expectedScripts) {
  var test = this;

  function executeFunctionInInspectedPage() {
    // Since breakpoints are ignored in evals' calculate() function is
    // execute after zero-timeout so that the breakpoint is hit.
    test.evaluateInConsole_(
        'setTimeout("' + code + '" , 0)',
        function(resultText) {
          test.assertTrue(!isNaN(resultText),
                          'Failed to get timer id: ' + resultText);
        });
  }

  test._waitUntilScriptsAreParsed(
      expectedScripts, executeFunctionInInspectedPage);
};


/**
 * Waits until all the scripts are parsed and invokes the callback.
 */
TestSuite.prototype._waitUntilScriptsAreParsed = function(
    expectedScripts, callback) {
  var test = this;

  function waitForAllScripts() {
    if (test._scriptsAreParsed(expectedScripts)) {
      callback();
    } else {
      test.addSniffer(WebInspector, 'parsedScriptSource', waitForAllScripts);
    }
  }

  waitForAllScripts();
};


/**
 * Waits until all debugger scripts are parsed and executes 'a()' in the
 * inspected page.
 */
TestSuite.prototype._executeFunctionForStepTest = function() {
  this._executeCodeWhenScriptsAreParsed(
      'a()',
      ['debugger_step.html$', 'debugger_step.js$']);
};


/**
 * Tests step over in the debugger.
 */
TestSuite.prototype.testStepOver = function() {
  this.showPanel('scripts');
  var test = this;

  this._executeFunctionForStepTest();

  this._performSteps([
      {
        functionsOnStack: ['d','a','(anonymous function)'],
        lineNumber: 3,
        lineText: '    debugger;'
      },
      function() {
        document.getElementById('scripts-step-over').click();
      },
      {
        functionsOnStack: ['d','a','(anonymous function)'],
        lineNumber: 5,
        lineText: '  var y = fact(10);'
      },
      function() {
        document.getElementById('scripts-step-over').click();
      },
      {
        functionsOnStack: ['d','a','(anonymous function)'],
        lineNumber: 6,
        lineText: '  return y;'
      },
      function() {
        test.releaseControl();
      }
  ]);

  test.takeControl();
};


/**
 * Tests step out in the debugger.
 */
TestSuite.prototype.testStepOut = function() {
  this.showPanel('scripts');
  var test = this;

  this._executeFunctionForStepTest();

  this._performSteps([
      {
        functionsOnStack: ['d','a','(anonymous function)'],
        lineNumber: 3,
        lineText: '    debugger;'
      },
      function() {
        document.getElementById('scripts-step-out').click();
      },
      {
        functionsOnStack: ['a','(anonymous function)'],
        lineNumber: 8,
        lineText: '  printResult(result);'
      },
      function() {
        test.releaseControl();
      }
  ]);

  test.takeControl();
};


/**
 * Tests step in in the debugger.
 */
TestSuite.prototype.testStepIn = function() {
  this.showPanel('scripts');
  var test = this;

  this._executeFunctionForStepTest();

  this._performSteps([
      {
        functionsOnStack: ['d','a','(anonymous function)'],
        lineNumber: 3,
        lineText: '    debugger;'
      },
      function() {
        document.getElementById('scripts-step-over').click();
      },
      {
        functionsOnStack: ['d','a','(anonymous function)'],
        lineNumber: 5,
        lineText: '  var y = fact(10);'
      },
      function() {
        document.getElementById('scripts-step-into').click();
      },
      {
        functionsOnStack: ['fact','d','a','(anonymous function)'],
        lineNumber: 15,
        lineText: '  return r;'
      },
      function() {
        test.releaseControl();
      }
  ]);

  test.takeControl();
};


/**
 * Gets a XPathResult matching given xpath.
 * @param {string} xpath
 * @param {number} resultType
 * @param {Node} opt_ancestor Context node. If not specified documentElement
 *     will be used
 * @return {XPathResult} Type of returned value is determined by 'resultType' parameter
 */

TestSuite.prototype._evaluateXpath = function(
    xpath, resultType, opt_ancestor) {
  if (!opt_ancestor) {
    opt_ancestor = document.documentElement;
  }
  try {
    return document.evaluate(xpath, opt_ancestor, null, resultType, null);
  } catch(e) {
    this.fail('Error in expression: "' + xpath + '".' + e);
  }
};


/**
 * Gets first Node matching given xpath.
 * @param {string} xpath
 * @param {Node} opt_ancestor Context node. If not specified documentElement
 *     will be used
 * @return {?Node}
 */
TestSuite.prototype._findNode = function(xpath, opt_ancestor) {
  var result = this._evaluateXpath(xpath, XPathResult.FIRST_ORDERED_NODE_TYPE,
      opt_ancestor).singleNodeValue;
  this.assertTrue(!!result, 'Cannot find node on path: ' + xpath);
  return result;
};


/**
 * Gets a text matching given xpath.
 * @param {string} xpath
 * @param {Node} opt_ancestor Context node. If not specified documentElement
 *     will be used
 * @return {?string}
 */
TestSuite.prototype._findText = function(xpath, opt_ancestor) {
  var result = this._evaluateXpath(xpath, XPathResult.STRING_TYPE,
      opt_ancestor).stringValue;
  this.assertTrue(!!result, 'Cannot find text on path: ' + xpath);
  return result;
};


/**
 * Gets an iterator over nodes matching given xpath.
 * @param {string} xpath
 * @param {Node} opt_ancestor Context node. If not specified, documentElement
 *     will be used
 * @return {XPathResult} Iterator over the nodes
 */
TestSuite.prototype._nodeIterator = function(xpath, opt_ancestor) {
  return this._evaluateXpath(xpath, XPathResult.ORDERED_NODE_ITERATOR_TYPE,
      opt_ancestor);
};


/**
 * Checks the scopeSectionDiv against the expectations.
 * @param {Node} scopeSectionDiv The section div
 * @param {Object} expectations Expectations dictionary
 */
TestSuite.prototype._checkScopeSectionDiv = function(
    scopeSectionDiv, expectations) {
  var scopeTitle = this._findText(
      './div[@class="header"]/div[@class="title"]/text()', scopeSectionDiv);
  this.assertEquals(expectations.title, scopeTitle,
      'Unexpected scope section title.');
  if (!expectations.properties) {
    return;
  }
  this.assertTrue(scopeSectionDiv.hasStyleClass('expanded'), 'Section "' +
      scopeTitle + '" is collapsed.');

  var propertyIt = this._nodeIterator('./ol[@class="properties"]/li',
                                      scopeSectionDiv);
  var propertyLi;
  var foundProps = [];
  while (propertyLi = propertyIt.iterateNext()) {
    var name = this._findText('./span[@class="name"]/text()', propertyLi);
    var value = this._findText('./span[@class="value"]/text()', propertyLi);
    this.assertTrue(!!name, 'Invalid variable name: "' + name + '"');
    this.assertTrue(name in expectations.properties,
                    'Unexpected property: ' + name);
    this.assertEquals(expectations.properties[name], value,
                      'Unexpected "' + name + '" property value.');
    delete expectations.properties[name];
    foundProps.push(name + ' = ' + value);
  }

  // Check that all expected properties were found.
  for (var p in expectations.properties) {
    this.fail('Property "' + p + '" was not found in scope "' + scopeTitle +
        '". Found properties: "' + foundProps.join(',') + '"');
  }
};


/**
 * Expands scope sections matching the filter and invokes the callback on
 * success.
 * @param {function(WebInspector.ObjectPropertiesSection, number):boolean}
 *     filter
 * @param {Function} callback
 */
TestSuite.prototype._expandScopeSections = function(filter, callback) {
  var sections = WebInspector.currentPanel.sidebarPanes.scopechain.sections;

  var toBeUpdatedCount = 0;
  function updateListener() {
    --toBeUpdatedCount;
    if (toBeUpdatedCount == 0) {
      // Report when all scopes are expanded and populated.
      callback();
    }
  }

  // Global scope is always the last one.
  for (var i = 0; i < sections.length - 1; i++) {
    var section = sections[i];
    if (!filter(sections, i)) {
      continue;
    }
    ++toBeUpdatedCount;
    var populated = section.populated;

    this._hookGetPropertiesCallback(updateListener,
      function() {
        section.expand();
        if (populated) {
          // Make sure 'updateProperties' callback will be called at least once
          // after it was overridden.
          section.update();
        }
      });
  }
};


/**
 * Tests that scopes can be expanded and contain expected data.
 */
TestSuite.prototype.testExpandScope = function() {
  this.showPanel('scripts');
  var test = this;

  this._executeCodeWhenScriptsAreParsed(
      'handleClick()',
      ['debugger_closure.html$']);

  this._waitForScriptPause(
      {
        functionsOnStack: ['innerFunction', 'handleClick',
                           '(anonymous function)'],
        lineNumber: 8,
        lineText: '    debugger;'
      },
      expandAllSectionsExceptGlobal);

  // Expanding Global scope takes for too long so we skeep it.
  function expandAllSectionsExceptGlobal() {
      test._expandScopeSections(function(sections, i) {
        return i < sections.length - 1;
      },
      examineScopes /* When all scopes are expanded and populated check
          them. */);
  }

  // Check scope sections contents.
  function examineScopes() {
    var scopeVariablesSection = test._findNode('//div[@id="scripts-sidebar"]/' +
        'div[div[@class="title"]/text()="Scope Variables"]');
    var expectedScopes = [
      {
        title: 'Local',
        properties: {
          x:2009,
          innerFunctionLocalVar:2011,
          'this': 'global',
        }
      },
      {
        title: 'Closure',
        properties: {
          n:'TextParam',
          makeClosureLocalVar:'local.TextParam',
        }
      },
      {
        title: 'Global',
      },
    ];
    var it = test._nodeIterator('./div[@class="body"]/div',
                                scopeVariablesSection);
    var scopeIndex = 0;
    var scopeDiv;
    while (scopeDiv = it.iterateNext()) {
       test.assertTrue(scopeIndex < expectedScopes.length,
                       'Too many scopes.');
       test._checkScopeSectionDiv(scopeDiv, expectedScopes[scopeIndex]);
       ++scopeIndex;
    }
    test.assertEquals(expectedScopes.length, scopeIndex,
                      'Unexpected number of scopes.');

    test.releaseControl();
  }

  test.takeControl();
};


/**
 * Returns child tree element for a property with given name.
 * @param {TreeElement} parent Parent tree element.
 * @param {string} childName
 * @param {string} objectPath Path to the object. Will be printed in the case
 *     of failure.
 * @return {TreeElement}
 */
TestSuite.prototype._findChildProperty = function(
    parent, childName, objectPath) {
  var children = parent.children;
  for (var i = 0; i < children.length; i++) {
    var treeElement = children[i];
    var property = treeElement.property;
    if (property.name == childName) {
      return treeElement;
    }
  }
  this.fail('Cannot find property "' + childName + '" in ' + objectPath);
};


/**
 * Executes the 'code' with InjectedScriptAccess.getProperties overriden
 * so that all callbacks passed to InjectedScriptAccess.getProperties are
 * extended with the 'hook'.
 * @param {Function} hook The hook function.
 * @param {Function} code A code snippet to be executed.
 */
TestSuite.prototype._hookGetPropertiesCallback = function(hook, code) {
  var orig = InjectedScriptAccess.getProperties;
  InjectedScriptAccess.getProperties = function(objectProxy,
      ignoreHasOwnProperty, callback) {
    orig.call(InjectedScriptAccess, objectProxy, ignoreHasOwnProperty,
        function() {
          callback.apply(this, arguments);
          hook();
        });
  };
  try {
    code();
  } finally {
    InjectedScriptAccess.getProperties = orig;
  }
};


/**
 * Tests that all elements in prototype chain of an object have expected
 * intrinic proprties(__proto__, constructor, prototype).
 */
TestSuite.prototype.testDebugIntrinsicProperties = function() {
  this.showPanel('scripts');
  var test = this;

  this._executeCodeWhenScriptsAreParsed(
      'handleClick()',
      ['debugger_intrinsic_properties.html$']);

  this._waitForScriptPause(
      {
        functionsOnStack: ['callDebugger', 'handleClick',
                           '(anonymous function)'],
        lineNumber: 29,
        lineText: '  debugger;'
      },
      expandLocalScope);

  var localScopeSection = null;
  function expandLocalScope() {
    test._expandScopeSections(function(sections, i) {
        if (i == 0) {
          test.assertTrue(sections[i].object.isLocal, 'Scope #0 is not Local.');
          localScopeSection = sections[i];
          return true;
        }
        return false;
      },
      examineLocalScope);
  }

  function examineLocalScope() {
    var scopeExpectations = [
      'a', 'Object', [
        'constructor', 'function Child()', [
          'constructor', 'function Function()', null,
          'name', 'Child', null,
          'prototype', 'Object', [
            'childProtoField', '21', null
          ]
        ],

        '__proto__', 'Object', [
          '__proto__', 'Object', [
            '__proto__', 'Object', [
              '__proto__', 'null', null,
              'constructor', 'function Object()', null,
            ],
            'constructor', 'function Parent()', [
              'name', 'Parent', null,
              'prototype', 'Object', [
                'parentProtoField', '11', null,
              ]
            ],
            'parentProtoField', '11', null,
          ],
          'constructor', 'function Child()', null,
          'childProtoField', '21', null,
        ],

        'parentField', '10', null,
        'childField', '20', null,
      ]
    ];

    checkProperty(
        localScopeSection.propertiesTreeOutline,
        '<Local Scope>',
        scopeExpectations);
  }

  var propQueue = [];
  var index = 0;
  var expectedFinalIndex = 8;

  function expandAndCheckNextProperty() {
    if (index == propQueue.length) {
      test.assertEquals(expectedFinalIndex, index,
          'Unexpected number of expanded objects.');
      test.releaseControl();
      return;
    }

    // Read next property data from the queue.
    var treeElement = propQueue[index].treeElement;
    var path = propQueue[index].path;
    var expectations = propQueue[index].expectations;
    index++;

    // Expand the property.
    test._hookGetPropertiesCallback(function() {
          checkProperty(treeElement, path, expectations);
        },
        function() {
          treeElement.expand();
        });
  }

  function checkProperty(treeElement, path, expectations) {
    for (var i = 0; i < expectations.length; i += 3) {
      var name = expectations[i];
      var description = expectations[i+1];
      var value = expectations[i+2];

      var propertyPath = path + '.' + name;
      var propertyTreeElement = test._findChildProperty(
          treeElement, name, path);
      test.assertTrue(propertyTreeElement,
          'Property "' + propertyPath + '" not found.');
      test.assertEquals(description,
          propertyTreeElement.property.value.description,
          'Unexpected "' + propertyPath + '" description.');
      if (value) {
        // Schedule property content check.
        propQueue.push({
          treeElement: propertyTreeElement,
          path: propertyPath,
          expectations: value,
        });
      }
    }
    // Check next property in the queue.
    expandAndCheckNextProperty();
  }

  test.takeControl();
};


/**
 * Tests 'Pause' button will pause debugger when a snippet is evaluated.
 */
TestSuite.prototype.testPauseInEval = function() {
  this.showPanel('scripts');

  var test = this;

  var pauseButton = document.getElementById('scripts-pause');
  pauseButton.click();

  devtools.tools.evaluateJavaScript('fib(10)');

  this.addSniffer(WebInspector, 'pausedScript',
      function() {
        test.releaseControl();
      });

  test.takeControl();
};


/**
 * Key event with given key identifier.
 */
TestSuite.KeyEvent = function(key) {
  this.keyIdentifier = key;
};
TestSuite.KeyEvent.prototype.preventDefault = function() {};
TestSuite.KeyEvent.prototype.stopPropagation = function() {};


/**
 * Tests console eval.
 */
TestSuite.prototype.testConsoleEval = function() {
  var test = this;
  this.evaluateInConsole_('123',
      function(resultText) {
        test.assertEquals('123', resultText);
        test.releaseControl();
      });

  this.takeControl();
};


/**
 * Tests console log.
 */
TestSuite.prototype.testConsoleLog = function() {
  WebInspector.console.visible = true;
  var messages = WebInspector.console.messages;
  var index = 0;

  var test = this;
  var assertNext = function(line, message, opt_class, opt_count, opt_substr) {
    var elem = messages[index++].toMessageElement();
    var clazz = elem.getAttribute('class');
    var expectation = (opt_count || '') + 'console_test_page.html:' +
        line + message;
    if (opt_substr) {
      test.assertContains(elem.textContent, expectation);
    } else {
      test.assertEquals(expectation, elem.textContent);
    }
    if (opt_class) {
      test.assertContains(clazz, 'console-' + opt_class);
    }
  };

  assertNext('5', 'log', 'log-level');
  assertNext('7', 'debug', 'log-level');
  assertNext('9', 'info', 'log-level');
  assertNext('11', 'warn', 'warning-level');
  assertNext('13', 'error', 'error-level');
  assertNext('15', 'Message format number 1, 2 and 3.5');
  assertNext('17', 'Message format for string');
  assertNext('19', 'Object Object');
  assertNext('22', 'repeated', 'log-level', 5);
  assertNext('26', 'count: 1');
  assertNext('26', 'count: 2');
  assertNext('29', 'group', 'group-title');
  index++;
  assertNext('33', 'timer:', 'log-level', '', true);
  assertNext('35', '1 2 3', 'log-level');
  assertNext('37', 'HTMLDocument', 'log-level');
  assertNext('39', '<html>', 'log-level', '', true);
};


/**
 * Tests eval of global objects.
 */
TestSuite.prototype.testEvalGlobal = function() {
  WebInspector.console.visible = true;

  var inputs = ['foo', 'foobar'];
  var expectations = ['foo', 'fooValue',
                      'foobar', 'ReferenceError: foobar is not defined'];

  // Do not change code below - simply add inputs and expectations above.
  var initEval = function(input) {
    WebInspector.console.prompt.text = input;
    WebInspector.console.promptElement.handleKeyEvent(
        new TestSuite.KeyEvent('Enter'));
  };
  var test = this;
  var messagesCount = 0;
  var inputIndex = 0;
  this.addSniffer(WebInspector.ConsoleView.prototype, 'addMessage',
      function(commandResult) {
        messagesCount++;
        if (messagesCount == expectations.length) {
          var messages = WebInspector.console.messages;
          for (var i = 0; i < expectations; ++i) {
            var elem = messages[i++].toMessageElement();
            test.assertEquals(elem.textContent, expectations[i]);
          }
          test.releaseControl();
        } else if (messagesCount % 2 == 0) {
          initEval(inputs[inputIndex++]);
        }
      }, true);

  initEval(inputs[inputIndex++]);
  this.takeControl();
};


/**
 * Tests that Storage panel can be open and that local DOM storage is added
 * to the panel.
 */
TestSuite.prototype.testShowStoragePanel = function() {
  var test = this;
  this.addSniffer(WebInspector.panels.storage, 'addDOMStorage',
      function(storage) {
        var orig = storage.getEntries;
        storage.getEntries = function(callback) {
          orig.call(this, function(entries) {
            callback(entries);
            test.releaseControl();
          });
        };
        try {
          WebInspector.currentPanel.selectDOMStorage(storage.id);
          storage.getEntries = orig;
        } catch (e) {
          test.fail('Exception in selectDOMStorage: ' + e);
        }
      });
  this.showPanel('storage');

  // Access localStorage so that it's pushed to the frontend.
  this.evaluateInConsole_(
      'setTimeout("localStorage.x = 10" , 0)',
      function(resultText) {
        test.assertTrue(!isNaN(resultText),
                        'Failed to get timer id: ' + resultText);
      });

  // Wait until DOM storage is added to the panel.
  this.takeControl();
};


/**
 * Test runner for the test suite.
 */
var uiTests = {};


/**
 * Run each test from the test suit on a fresh instance of the suite.
 */
uiTests.runAllTests = function() {
  // For debugging purposes.
  for (var name in TestSuite.prototype) {
    if (name.substring(0, 4) == 'test' &&
        typeof TestSuite.prototype[name] == 'function') {
      uiTests.runTest(name);
    }
  }
};


/**
 * Run specified test on a fresh instance of the test suite.
 * @param {string} name Name of a test method from TestSuite class.
 */
uiTests.runTest = function(name) {
  new TestSuite().runTest(name);
};


}

